1. 18 Jun, 2019 1 commit
    • Kirill Smelkov's avatar
      Fix build for Python 3.7 · bca5f79e
      Kirill Smelkov authored
      Starting from Python 3.7 the place to keep exception state was changed:
      NOTE ZEO4 does not wok with Python3.7, because ZEO4 uses "async" for a
      variable and that breaks because starting from Python3.7 "async" became
      a keyword.
      After the fix wendelin.core tests pass under all python2.7, python3.6
      and python3.7.
  2. 23 May, 2019 1 commit
    • Kirill Smelkov's avatar
      bigfile/zodb: Resync _ZBigFileH to Connection.transaction_manager on every connection reopen · d9d6adec
      Kirill Smelkov authored
      This continues c7c01ce4 (bigfile/zodb: ZODB.Connection can migrate
      between threads on close/open and we have to care): Until now we were
      retrieving zconn.transaction_manager on _ZBigFileH init, and further using
      that transaction manager for every connection reopen. However that is
      not correct because on every reopen connection can be given new
      transaction manager.
      We were not practically hitting the bug because until recently ZODB was,
      by default, using the same ThreadTransactionManager manager instance as
      Connection.transaction_manager for all connections, and not doing all
      steps needed to keep _ZBigFileH.transaction_manager in sync to
      Connection was forgiven - a particular transaction manager that was used
      was TransactionManager instance implicitly associated with current
      thread by global threading.Local transaction.manager . However starting
      from ZODB 5.5.0 Connection code was changed to remember as
      .transaction_manager the particular TransactionManager instance without
      any threading.Local games:
      Given that we were not syncing properly that broke wendelin.core tests, for
          bigfile/tests/test_filezodb.py::test_bigfile_filezodb_vs_conn_migration Exception in thread Thread-1:
          Traceback (most recent call last):
            File "/usr/lib/python2.7/threading.py", line 801, in __bootstrap_inner
            File "/usr/lib/python2.7/threading.py", line 754, in run
              self.__target(*self.__args, **self.__kwargs)
            File "/home/kirr/src/wendelin/wendelin.core/bigfile/tests/test_filezodb.py", line 401, in T11
              transaction.commit()    # should be nothing
            File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/transaction/_manager.py", line 252, in commit
              return self.manager.commit()
            File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/transaction/_manager.py", line 131, in commit
              return self.get().commit()
            File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/transaction/_transaction.py", line 298, in commit
              self._synchronizers.map(lambda s: s.beforeCompletion(self))
            File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/transaction/weakset.py", line 61, in map
            File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/transaction/_transaction.py", line 298, in <lambda>
              self._synchronizers.map(lambda s: s.beforeCompletion(self))
            File "/home/kirr/src/wendelin/wendelin.core/bigfile/file_zodb.py", line 671, in beforeCompletion
              assert txn is zconn.transaction_manager.get()
      What is happening here is that one thread used the connection and
      ZBigFile/_ZBigFileH associated with it, then the connection was closed
      and released to DB pool. Then the connection was reopened but by another
      thread and thus with different TransactionManager instance and oops -
      _ZBigFileH.transaction_manager is different because it is
      TransactionManager instance that was used by the first thread.
      Fix it by resyncing _ZBigFileH.transaction_manager on every
      connection reopen. No new test as existing tests already cover the
      problem when run with ZODB >= 5.5.0 .
  3. 11 Jan, 2019 1 commit
    • Kirill Smelkov's avatar
      bigfile/py: Properly untrack PyVMA from GC before dealloc · d97641d2
      Kirill Smelkov authored
      On a testing instance we started to see segfaults in pyvma_dealloc()
      with inside calls to vma_unmap but with NULL pyvma->fileh. That was
      strange, becuse before calling vma_unmap(), the code explicitly checks
      whether pyvma->fileh is !NULL.
      That was, as it turned out, due to pyvma_dealloc being called twice at the
      same time from two python threads. Here is how that was possible:
      T1 decrefs pyvma and finds its reference count drops to zero. It calls
      pyvma_dealloc. From there vma_unmap() is called, which calls virt_lock()
      and that releases GIL first. Another thread T2 was waiting for GIL, it
      acquires it, does some work at python level and somehow triggers GC.
      Since PyVMA supports cyclic GC, it was on GC list and thus GC calls
      dealloc for the same vma again. Here is how it looks in the backtraces:
      	#0  0x00007f6aefc57827 in futex_abstimed_wait_cancelable (private=0, abstime=0x0, expected=0, futex_word=0x1e011d0) at ../sysdeps/unix/sysv/linux/futex-internal.h:205
      	#1  do_futex_wait (sem=sem@entry=0x1e011d0, abstime=0x0) at sem_waitcommon.c:111
      	#2  0x00007f6aefc578d4 in __new_sem_wait_slow (sem=0x1e011d0, abstime=0x0) at sem_waitcommon.c:181
      	#3  0x00007f6aefc5797a in __new_sem_wait (sem=<optimized out>) at sem_wait.c:29
      	#4  0x00000000004ffbc4 in PyThread_acquire_lock ()
      	#5  0x00000000004dbe8a in PyEval_RestoreThread ()
      	#6  0x00007f6ac6d3b8fc in py_gil_retake_if_waslocked (arg=0x4f18f00) at bigfile/_bigfile.c:1048
      	#7  0x00007f6ac6d3dcfc in virt_gil_retake_if_waslocked (gilstate=0x4f18f00) at bigfile/virtmem.c:78
      	#8  0x00007f6ac6d3dd30 in virt_lock () at bigfile/virtmem.c:92
      	#9  0x00007f6ac6d3e724 in vma_unmap (vma=0x7f6a7e0c4100) at bigfile/virtmem.c:271
      	#10 0x00007f6ac6d3a0bc in pyvma_dealloc (pyvma0=0x7f6a7e0c40e0) at bigfile/_bigfile.c:284
      	#13 0x00000000004d76b0 in PyEval_EvalFrameEx ()
      	#5  0x00007f6ac6d3a081 in pyvma_dealloc (pyvma0=0x7f6a7e0c40e0) at bigfile/_bigfile.c:276
      	#6  0x0000000000500450 in ?? ()
      	#7  0x00000000004ffd82 in _PyObject_GC_New ()
      	#8  0x0000000000485392 in PyList_New ()
      	#9  0x00000000004d3bff in PyEval_EvalFrameEx ()
      T2 does the work of vma_unmap and clears C-level vma. Then, when T1 wakes up and
      returns to vma_unmap, it sees vma->file and all other fields cleared -> oops
      Fix it by removing pyvma from GC list before going to do actual destruction.
      This way if a concurrent GC triggers, it won't see the vma object on its list,
      and thus won't have a chance to invoke its destructor the second time.
      The bug was introduced in 450ad804 (bigarray: ArrayRef support for BigArray)
      when PyVMA was changed to be cyclic-GC aware. However at that time, even Python
      documentation itself was not saying PyObject_GC_UnTrack is needed, as it was
      added only in 2.7.15 after finding that many types in CPython itself are
      vulnerable to similar segfaults:
      It is pity, that CPython took the approach to force all type authors to
      care to invoke PyObject_GC_UnTrack explicitly, instead of doing that
      automatically in Python runtime before calling tp_dealloc.
      /cc @Tyagov, @klaus
      /reviewed-on !11
  4. 17 Apr, 2018 1 commit
    • Kirill Smelkov's avatar
      tests: Explicitly close ZODB connections for places with warnings found by previous patch · 01b995a4
      Kirill Smelkov authored
      bigfile/tests/test_filezodb.py ........W: testdb: teardown: <Connection at 7f8fe2b43b90> left not closed by test code; opened by:
        File "/home/kirr/src/wendelin/wendelin.core/bigfile/tests/test_filezodb.py", line 754, in test_bigfile_zblk1_zdata_reuse
        File "/home/kirr/src/wendelin/wendelin.core/bigfile/tests/test_filezodb.py", line 759, in _test_bigfile_zblk1_zdata_reuse
          root = dbopen()
        File "/home/kirr/src/wendelin/wendelin.core/bigfile/tests/test_filezodb.py", line 47, in dbopen
          return testdb.dbopen()
        File "/home/kirr/src/wendelin/wendelin.core/lib/testing.py", line 188, in dbopen
          self.connv.append( (weakref.ref(conn), ''.join(traceback.format_stack())) )
      lib/tests/test_zodb.py .W: testdb: teardown: <Connection at 7f8fe26f13d0> left not closed by test code; opened by:
        File "/home/kirr/src/wendelin/wendelin.core/lib/tests/test_zodb.py", line 49, in test_deactivate_btree
          root = dbopen()
        File "/home/kirr/src/wendelin/wendelin.core/lib/tests/test_zodb.py", line 30, in dbopen
          return testdb.dbopen()
        File "/home/kirr/src/wendelin/wendelin.core/lib/testing.py", line 188, in dbopen
          self.connv.append( (weakref.ref(conn), ''.join(traceback.format_stack())) )
  5. 02 Apr, 2018 1 commit
    • Kirill Smelkov's avatar
      bigarray: ArrayRef support for BigArray · 450ad804
      Kirill Smelkov authored
      Array reference could be useful in situations where one needs to pass arrays
      between processes and instead of copying array data, leverage the fact that
      top-level array, for example ZBigArray, is already persisted separately, and
      only send small amount of information referencing data in question.
      BigArray is not regular NumPy array and so needs explicit support in
      ArrayRef code to find root object and indices. This patch adds such
      support via the following way:
      - when BigArray.__getitem__ creates VMA, it remembers in the VMA
        the top-level BigArray object under which this VMA was created.
      - when ArrayRef is finding root, it can detect such VMAs, because it will
        be pointed to by the most top regular ndarray's .base, and in turn gets
        top-level BigArray object from the VMA.
      - further all indices computations are performed, similarly to complete regular
        ndarrays case, on ndarrays root and a. But in the end .lo and .hi are
        adjusted for the corresponding offset of where root is inside whole
      - there is no need to adjust .deref() at all.
      For remembering information into a VMA and also to be able to get
      (readonly) its mapping addresses _bigfile.c extension has to be extended
      a bit. Since we are now storing arbitrary python object attached to
      PyVMA - it can create cycles - and so PyVMA accordingly adjusted to
      support cyclic garbage collector.
      Please see the patch itself for more details and comments.
  6. 31 Jan, 2018 1 commit
    • Kirill Smelkov's avatar
      bigfile/virtmem: Fix build with recent glibc · c3cc8a99
      Kirill Smelkov authored
      It was
      	bigfile/pagefault.c:45:36: warning: ‘struct ucontext’ declared inside parameter list will not be visible outside of this definition or declaration
      	 static int faulted_by(const struct ucontext *uc);
      	bigfile/pagefault.c: In function ‘on_pagefault’:
      	bigfile/pagefault.c:59:24: warning: passing argument 1 of ‘faulted_by’ from incompatible pointer type [-Wincompatible-pointer-types]
      	     write = faulted_by(uc);
      	bigfile/pagefault.c:45:12: note: expected ‘const struct ucontext *’ but argument is of type ‘struct ucontext *’
      	 static int faulted_by(const struct ucontext *uc);
      	bigfile/pagefault.c: At top level:
      	bigfile/pagefault.c:208:36: warning: ‘struct ucontext’ declared inside parameter list will not be visible outside of this definition or declaration
      	 static int faulted_by(const struct ucontext *uc)
      	bigfile/pagefault.c:208:12: error: conflicting types for ‘faulted_by’
      	 static int faulted_by(const struct ucontext *uc)
      	bigfile/pagefault.c:45:12: note: previous declaration of ‘faulted_by’ was here
      	 static int faulted_by(const struct ucontext *uc);
      	bigfile/pagefault.c: In function ‘faulted_by’:
      	bigfile/pagefault.c:217:15: error: dereferencing pointer to incomplete type ‘const struct ucontext’
      	     write = uc->uc_mcontext.gregs[REG_ERR] & 0x2;
      	bigfile/pagefault.c: At top level:
      	bigfile/pagefault.c:45:12: warning: ‘faulted_by’ used but never defined
      	 static int faulted_by(const struct ucontext *uc);
      	bigfile/pagefault.c:208:12: warning: ‘faulted_by’ defined but not used [-Wunused-function]
      	 static int faulted_by(const struct ucontext *uc)
      Change to using ucontext_t because apparently there is no `struct
      ucontext` anymore (and man for sigaction says 3rd parameter to hander is
      of type `ucontext_t *` - not `struct ucontext *` - cast to `void *`)
      Explicitly include <ucontext.h> because we are dereferencing ucontext_t,
      even though today it appears to be included by <signal.h>.
  7. 12 Dec, 2017 1 commit
    • Kirill Smelkov's avatar
      virtmem: Benchmarks for pagefault handling · 3cfc2728
      Kirill Smelkov authored
      Benchmark the time it takes for virtmem to handle pagefault with noop
      loadblk for loadblk both implemented in C and in Python.
      On my computer it is:
      	name          µs/op
      	PagefaultC    269 ± 0%
      	pagefault_py  291 ± 0%
      Quite a big time in other words.
      It turned out to be mostly spent in fallocate'ing pages on tmpfs from
      /dev/shm. Part of the above 269 µs/op is taken by freeing (reclaiming)
      pages back when benchmarking work size exceed /dev/shm size, and part to
      If I limit the work size (via npage in benchmem.c) to be less than whole
      /dev/shm it starts to be ~ 170 µs/op and with additional tracing it
      shows as something like this:
          	.. on_pagefault_start   0.954 µs
          	.. vma_on_pagefault_pre 0.954 µs
          	.. ramh_alloc_page_pre  0.954 µs
          	.. ramh_alloc_page      169.992 µs
          	.. vma_on_pagefault     172.853 µs
          	.. vma_on_pagefault_pre 172.853 µs
          	.. vma_on_pagefault     174.046 µs
          	.. on_pagefault_end     174.046 µs
          	.. whole:               171.900 µs
      so almost all time is spent in ramh_alloc_page which is doing the fallocate:
      Simple benchmark[1] confirmed it is indeed the case for fallocate(tmpfs) to be
      relatively slow[2] (and that for recent kernels it regressed somewhat
      compared to Linux 3.16). Profile flamegraph for that benchmark[3] shows
      internal loading of shmem_fallocate which for 1 hardware page is not
      that too slow (e.g. <1µs) but when a request comes for a region
      internally performs it page by page and so accumulates that ~ 170µs for 2M.
      I've tried to briefly rerun the benchmark with huge pages activated on /dev/shm via
      	mount /dev/shm -o huge=always,remount
      as both regular user and as root but it was executing several times
      slower. Probably something to investigate more later.
      [1] https://lab.nexedi.com/kirr/misc/blob/4f84a06e/tmpfs/t_fallocate.c
      [2] https://lab.nexedi.com/kirr/misc/blob/4f84a06e/tmpfs/1.txt
      [3] https://lab.nexedi.com/kirr/misc/raw/4f84a06e/tmpfs/fallocate-2M-nohuge.svg
  8. 01 Dec, 2017 1 commit
  9. 24 Oct, 2017 1 commit
    • Kirill Smelkov's avatar
      Relicense to GPLv3+ with wide exception for all Free Software / Open Source... · f11386a4
      Kirill Smelkov authored
      Relicense to GPLv3+ with wide exception for all Free Software / Open Source projects + Business options.
      Nexedi stack is licensed under Free Software licenses with various exceptions
      that cover three business cases:
      - Free Software
      - Proprietary Software
      - Rebranding
      As long as one intends to develop Free Software based on Nexedi stack, no
      license cost is involved. Developing proprietary software based on Nexedi stack
      may require a proprietary exception license. Rebranding Nexedi stack is
      prohibited unless rebranding license is acquired.
      Through this licensing approach, Nexedi expects to encourage Free Software
      development without restrictions and at the same time create a framework for
      proprietary software to contribute to the long term sustainability of the
      Nexedi stack.
      Please see https://www.nexedi.com/licensing for details, rationale and options.
  10. 21 Aug, 2017 1 commit
    • Kirill Smelkov's avatar
      bigfile/py: Don't forget to clear exception state after retrieving pybuf referrers · 4228d8b6
      Kirill Smelkov authored
      A buffer object (pybuf) is passed by C-level loadblk to python loadblk
      implementation. Since pybuf points to memory that will go away after
      loadblk call returns to virtmem, PyBigFile tries hard to make sure
      nothing stays referencing pybuf so it can be released.
      It tries to:
      1. automatically GC cycles referencing pybuf (9aa6a5d7 "bigfile/py: Teach
         loadblk() to automatically break reference cycles to pybuf")
      2. replace pybuf with stub object if a calling frame referencing it still
         stays alive (61b18a40 "bigfile/py/loadblk: Replace pybuf with a stub
         object in calling frame in case it stays alive")
      3. and as a last resort unpins pybuf from original buffer memeory to
         point it to NULL (024c246c "bigfile/py/loadblk: Resort to pybuf
         unpinning, if nothing helps")
      Step #1 invokes GC. Step #2 calls gc.get_referrers(pybuf) and looks for
      frames in there.
      The gc.get_referrers() call happens at python level with allocating some
      objects, e.g. tuple to pass arguments, resulting list etc. And we all
      know that any object allocation might cause automatic garbage
      collection, and GC'ing can in turn ran arbitrary code due to __del__ in
      release objects and weakrefs callbacks.
      At a first glance the scenario that GC will be triggered at step #2
      looks unrealistic because the GC was just run at step #1 and it is only
      a few objects being allocated for the call at step #2. However if
      arbitrary code runs from under GC it can create new garbage and thus
      upon returning from gc.collect() the garbage list is not empty as the
      following program demonstrates:
          ---- 8< ----
          import gc
          # just an object we can set attributes on
          class X:
          # call f on __del__
          class DelCall:
              def __init__(self, f):
                  self.f = f
              def __del__(self):
          # _mkgarbage creates n objects of garbage kept referenced from an object cycle
          # so that only cyclic GC can free them.
          def _mkgarbage(n):
              # cycle
              a, b = X(), X()
              a.b, b.a = b, a
              # cycle references [n] garbage
              a.objv = [X() for _ in range(n)]
              return a
          # mkgarbage creates cycled garbage and arranges for twice more garbage to be
          # created when original garbage is collected
          def mkgarbage(n):
              a = _mkgarbage(n)
              a.ondel = DelCall(lambda : _mkgarbage(2*n))
          def main():
              for i in xrange(10):
                  print '> %s' % (gc.get_count(),)
                  n = gc.collect()
                  print '< %s' % (gc.get_count(),)
          ---- 8< ----
          kirr@deco:~/tmp/trashme/t$ ./gcmoregarbage.py
          > (482, 11, 0)
          < (1581, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
          > (531, 3, 0)
          < (2070, 0, 0)
      here lines starting with "<" show amount of live garbage objects after
      gc.collect() call has been finished.
      This way on a busy server there could be arrangements when GC is
      explicitly ran at step #1 and then automatically run at step #2 (because of
      gc.get_referrers() python-level call) and from under GC #2 arbitrary code runs
      thus potentially mutating exception state which shows in logs as
          bigfile/_bigfile.c:685: pybigfile_loadblk: Assertion `!(ts->exc_type || ts->exc_value || ts->exc_traceback)' failed.
      So don't assume we end with clean exception state after collecting pybuf
      referrers and just clear exception state once again as we do after explicit GC.
      Don't make a similar assumption for buffer unpinning as an object is
      decrefed there and in theory this can run some code.
      A test is added to automatically exercise exception state clearing for
      get_referrers code path via approach similar to demonstrated in above -
      - we generate more garbage from under garbage and also arrange for
      finalizers, which mutate exceptions state, to be run at GC #2.
      The test without the fix applied fails like this:
          bigfile/_bigfile.c:710 pybigfile_loadblk WARN: python thread-state found with handled but not cleared exception state
          bigfile/_bigfile.c:711 pybigfile_loadblk WARN: I will dump it and then crash
          ts->exc_type:   None
          ts->exc_value:  <nil>
          ts->exc_traceback:      <nil>
          Segmentation fault (core dumped)
      The None in ts->exc_type and nil value and traceback are probably coming from
      here in cpython runtime:
      Since this took some time to find, more diagnostics is also added before
      BUG_ONs corresponding to finding unclean exception state.
  11. 17 Aug, 2017 1 commit
    • Kirill Smelkov's avatar
      bigfile/py: Stop caring about sys.exc_{type,value,traceback} variables · 3804cc39
      Kirill Smelkov authored
      Before py3k python stores exception information not only in thread-local state
      but also globally in sys.exc_* variables (wrt sys.exc_info()) for
      "backward compatibility". However using them is not thread-safe as the
      following example demonstrates:
      ---- 8< ----
      from threading import Thread
      import sys
      def T1():
          print 'T1'
          while 1:
              exc_type = sys.exc_type
              if exc_type is not None:
                  print 'AAA: %r' % exc_type
      def f(): g()
      def g(): h()
      def h(): 1/0
      def T2():
          print 'T2'
          while 1:
      t1, t2 = Thread(target=T1), Thread(target=T2)
      t1.start(); t2.start()
      ---- 8< ----
      ---- 8< ----
      kirr@deco:~/tmp/trashme/t$ ./excthreads.py
      AAA: <type 'exceptions.ZeroDivisionError'>
      AAA: <type 'exceptions.ZeroDivisionError'>
      AAA: <type 'exceptions.ZeroDivisionError'>
      AAA: <type 'exceptions.ZeroDivisionError'>
      AAA: <type 'exceptions.ZeroDivisionError'>
      AAA: <type 'exceptions.ZeroDivisionError'>
      ---- 8< ----
      Because of the above nothing modern (I've explicitly checked at least CPython
      itself and Zope) uses this variables - wherever needed per-thread exception
      state is retrieved with sys.exc_info().
      So on wendelin.core side it is thus thankless job to try to preserve sys.exc_*
      vars state because on a busy server they are literally changing all the -
      arbitrary from the point of view of particular thread - time while its python
      code runs.
  12. 06 Jul, 2017 1 commit
    • Kirill Smelkov's avatar
      bigfile/virtmem: Don't forget to release fileh->writeout_inprogress on storeblk error · 87bf4908
      Kirill Smelkov authored
      Commit fb4bfb32 (bigfile/virtmem: Do storeblk() with virtmem lock
      released) added bug-protection to fileh_dirty_writeout() so that it could
      not be called twice at the same time or in parallel with other functions
      which modify pages.
      However it missed the code path when storeblk() call returned with error
      and whole writeout was thus erroring out, but with fileh->writeout_inprogress
      still left set to 1 incorrectly.
      This was leading to things like
          bigfile/virtmem.c:419: fileh_dirty_discard: Assertion `!(fileh->writeout_inprogress)' failed.
      and crashes.
      Fix it.
  13. 24 Mar, 2017 1 commit
  14. 16 Jan, 2017 4 commits
    • Kirill Smelkov's avatar
      bigfile/py/loadblk: Resort to pybuf unpinninf, if nothing helps · 024c246c
      Kirill Smelkov authored
      There are situations possible when both exc_traceback and frame objects are
      garbage-collected, but frame's f_locals remains not collected because e.g. it
      was explicitly added to somewhere. We cannot detect such cases (dicts are not
      listed in referrers).
      So if nothing helped, as a last resort, unpin pybuf from its original
      memory and make it point to zero-sized NULL.
      In general this is not strictly correct to do as other buffers &
      memoryview objects created from pybuf, copy its pointer on
      initialization and thus pybuf unpinning won't adjust them.
      However we require BigFile implementations to make sure not to use
      such-created objects, if any, after return from loadblk().
      Finally fixes #7
    • Kirill Smelkov's avatar
      bigfile/py: Factor out code to "unpin" a buffer to separate functions · 3a8e1beb
      Kirill Smelkov authored
      This code was added in 6da5172e (bigfile/py: Teach storeblk() how to
      correctly propagate traceback on error) to unpin a storeblk pybuf to not
      care whether its refcount == 1 - this way to be able to propagate python
      error upper not caring whether pybuf is still referenced or not.
      9aa6a5d7 (bigfile/py: Teach loadblk() to automatically break reference
      cycles to pybuf) adds a note that such unpinning is not strictly
      correct: becuase of other buffer objects were created from pybuf - they
      are copying pointers on initialization and unpinning pybuf won't adjust
      However for loadblk codepath it turned out (see next patch) it is not
      completely possible to unreference pybuf in all cases. For this reason
      loadblk will be falling back to unpinning too.
      As a preparatory step move common code to shared functions.
    • Kirill Smelkov's avatar
      bigfile/py/loadblk: Replace pybuf with a stub object in calling frame in case it stays alive · 61b18a40
      Kirill Smelkov authored
      It turns out some code wants to store tracebacks e.g. for further
      logging/whatever. This way GC won't help to free up references to pybuf.
      However if pybuf remain referenced only from calling frames, we can
      change there reference to pybuf to a stub object "<pybuf>" and this way
      remove the reference.
      With added test but without loadblk changes the failure would be as:
          pybigfile_loadblk WARN: pybuf->ob_refcnt != 1 even after GC:
          pybuf (ob_refcnt=2):    <read-write buffer ptr 0x7fae4911f000, size 2097152 at 0x7fae4998cef0>
          pybuf referrers:        [<frame object at 0x556daff41aa0>]		<-- NOTE
          bigfile/_bigfile.c:613 pybigfile_loadblk        BUG!
    • Kirill Smelkov's avatar
      bigfile/py/test_basic: Rework exception testing codepath so it is active on py3 also · f01b27d2
      Kirill Smelkov authored
      As comments being removed states "on python3 exception state is cleared
      upon exiting from `except`" - so let's move exc_* fetching under except
      clause - this way we'll get correct exception objects on both py2 and py3.
  15. 12 Jan, 2017 2 commits
    • Kirill Smelkov's avatar
      bigfile/py: Factor outcode to get list of objects that refer to obj to XPyObject_GetReferrers() · ca7c1b6d
      Kirill Smelkov authored
      We'll need it in next patch to get and analyze this list.
    • Kirill Smelkov's avatar
      bigfile/py: Dump pybuf referrers if pybuf->ob_refcnt != 1 before dying in loadblk epilogue · 20b41a5a
      Kirill Smelkov authored
      Instead of only printing "BUG" let's print information about objects
      which still refer to pybuf - to help debugging.
      For example with the following artificial pybuf leak
      diff --git a/bigfile/tests/test_basic.py b/bigfile/tests/test_basic.py
      index c737621..f5e057a 100644
      --- a/bigfile/tests/test_basic.py
      +++ b/bigfile/tests/test_basic.py
      @@ -126,6 +126,7 @@ def test_basic():
       # test that python exception state is preserved across pagefaulting
       def test_pagefault_savestate():
      +    zzz = []
           class BadFile(BigFile):
               def loadblk(self, blk, buf):
                   # simulate some errors in-between to overwrite thread exception
      @@ -154,6 +155,7 @@ def loadblk(self, blk, buf):
                   # which result in holding additional ref to buf, but loadblk caller
                   # will detect and handle this situation via garbage-collecting
                   # above cycle.
      +            zzz.append(buf)
                   self.loadblk_run = 1
      it dies this way:
          bigfile/_bigfile.c:567 pybigfile_loadblk WARN: pybuf->ob_refcnt != 1 even after GC:
          pybuf (ob_refcnt=2):    <read-write buffer ptr 0x7f08d3e88000, size 2097152 at 0x7f08d48b7070>
          pybuf referrers:        [[<read-write buffer ptr 0x7f08d3e88000, size 2097152 at 0x7f08d48b7070>]]
          bigfile/_bigfile.c:573 pybigfile_loadblk        BUG!
  16. 11 Jan, 2017 2 commits
    • Kirill Smelkov's avatar
      bigfile/py: Teach loadblk() to automatically break reference cycles to pybuf · 9aa6a5d7
      Kirill Smelkov authored
      Because otherwise we bug on pybuf->ob_refcnt != 1.
      Such cycles might happen if inside loadblk implementation an exception
      is internally raised and then caught even in deeply internal function
      which does not receive pybuf as argument or by some other way:
      	_, _, exc_traceback = sys.exc_info()
      there is a reference loop created:
      	  |        ^
      	  |        |
      	  v     .f_localsplus
      and since exc_traceback object holds reference to deepest frame, which via f_back
      will be holding reference to frames up to frame with pybuf argument, it
      will result in additional reference to pybuf being held until the above
      cycle is garbage collected.
      So to solve the problem while leaving loadblk, if pybuf->ob_refcnt !=
      let's first do garbage-collection, and only then recheck left
      references. After GC reference-loops created by exceptions should go
      NOTE PyGC_Collect() (C way to call gc.collect()) always performs
          GC - it is not affected by gc.disable() which disables only
          _automatic_ garbage collection.
      NOTE it turned out out storeblk logic to unpin pybuf (see
          6da5172e "bigfile/py: Teach storeblk() how to correctly propagate
          traceback on error") is flawed, because when e.g. creating memoryview
          from pybuf internal pointer is copied and then clearing original buf
          does not result in clearing the copy.
      NOTE it is ok to do gc.collect() from under sighandler - at least we are
          already doing it for a long time via running non-trivial python code
          which for sure triggers automatic GC from time to time (see also
          786d418d "bigfile: Simple test that we can handle GC from-under
          sighandler" for the reference)
      Fixes: #7
    • Kirill Smelkov's avatar
      bigfile/py: Factor exception state clearing code to XPyErr_FullClear() · b4c269eb
      Kirill Smelkov authored
      In the next patch we will need to use this from several places.
  17. 10 Jan, 2017 3 commits
    • Kirill Smelkov's avatar
      bigfile/virtmem: Do storeblk() with virtmem lock released · fb4bfb32
      Kirill Smelkov authored
      Like with loadblk (see f49c11a3 "bigfile/virtmem: Do loadblk() with
      virtmem lock released" for the reference) storeblk() calls are
      potentially slow and external code that serves the call can take other
      locks in addition to virtmem lock taken by virtmem subsystem.
      If that "other locks" are also taken before external code calls e.g.
      with fileh_invalidate_page() in different codepath - a deadlock can happen:
            T1                  T2
            commit              invalidation-from-server received
            V -> storeblk
                                Z   <- ClientStorage.invalidateTransaction()
            Z -> zeo.store
                                V   <- fileh_invalidate_page (of unrelated page)
      The solution to avoid deadlock, like for loadblk case, is to call storeblk()
      with virtmem lock released.
      However unlike loadblk which can be invoked at any time, storeblk is
      invoked at commit time only so for storeblk case we handle rules for making
      sure virtmem stays consistent after virtmem lock is retaken differently:
      1. We disallow several parallel writeouts for one fileh. This way dirty
         pages handling logic can not mess up. This restriction is also
         consistent with ZODB 2 phase commit protocol where for a transaction
         commit logic is invoked/handled from only 1 thread.
      2. For the same reason we disallow discard while writeout is in
         progress. This is also consistent with ZODB 2 phase commit protocol
         where txn.tpc_abort() is not expected to be called at the same time
         with txn.commit().
      3. While writeout is in progress, for that fileh we disallow pages
         modifications and pages invalidations - because both operations would
         change at least fileh dirty pages list which is iterated over by
         writeout code with releasing/retaking the virtmem lock. By
         disallowing them we make sure fileh dirty pages list stays constant
         during whole fileh writeout.
         This restrictions are also consistent with ZODB commit semantics:
         - while an object is being stored into ZODB it is not expected it
           will be further modified or explicitly invalidated by client via
         - server initiated invalidations come into effect only at transaction
           boundaries - when new transaction is started, not during commit time.
      Also since now storeblk is called with virtmem lock released, for buffer
      to store we no longer can use present page mapping in some vma directly,
      because while virtmem lock is released that mappings can go away.
      Fixes: #6
    • Kirill Smelkov's avatar
      bigfile/tests/thread: Don't invalidate exactly same page to test general... · b0d1e540
      Kirill Smelkov authored
      bigfile/tests/thread: Don't invalidate exactly same page to test general virtmem deadlock on loadblk
      The main deadlock described in f49c11a3 (bigfile/virtmem: Do loadblk()
      with virtmem lock released) (V,Z in T1; Z,V in T2) can happen if in T2 V
      is taken for whatever reason - e.g. for invalidating completely
      unrelated page to what is being loaded in T1.
      For invalidation of the same page we have explicit separate
      test_thread_load_vs_invalidate() which verifies how loadblk handles this
      This patch prepares general test_thread_lock_vs_virtmem_lock() to also
      test V vs Z deadlock for storeblk() case - when it will be called with
      virtmem lock released: for storeblk it will be forbidden by virtmem
      rules to invalidate pages of fileh for which writeout is in progress.
      Updates: #6
    • Kirill Smelkov's avatar
      bigfile/virtmem: Maintain dirty pages list for a fileh · 8bb7f2f2
      Kirill Smelkov authored
      This allows writeout code not to scan whole pagemap to find dirty pages
      to write out, which should be faster.
      But more importantly iterating whole pagemap on writeout would become
      unsafe, when in upcoming patch storeblk() will be called with virt_lock
      released: because there pagemap could be modified e.g. due to processing
      other read accesses.
      So maintain fileh->dirty_pages list and use it when we need to go
      through dirtied pages.
      Updates: #6
  18. 09 Jan, 2017 1 commit
    • Kirill Smelkov's avatar
      bigfile/virtmem: Make sure pages are emitted to store in order · 43b6fdbc
      Kirill Smelkov authored
      Currently fileh_dirty_writeout() writes page via storeblk() in order -
      - those with lower ->f_pgoffset are stored first.
      This happens because current fileh_dirty_writeout() iterates whole
      pagemap to find dirty pages and pagemap iteration is ordered by
      In upcoming patch we'll rework writeout code not to iterate through
      whole pagemap, but only through dirty pages. However the property that
      pages are emitted in canonical order is useful, so let's make sure via
      tests this will stay preserved:
      In mkdirty2() we modify pages in 2, 0 order, but the latter code checks
      (via storeblk_trace()) they were actually stored in 0, 2 order.
  19. 14 Aug, 2016 1 commit
    • Kirill Smelkov's avatar
      bigfile/zodb/ZBlk1: Don't miss to deactivate/free internal .chunktab buckets in loadblkdata() · 542917d1
      Kirill Smelkov authored
      13c0c17c (bigfile/zodb: Format #1 which is optimized for small changes)
      used BTree to organize ZBlk1 block's chunks and for loadblkdata() added
      "TODO we are missing to free internal BTree structures on data load".
      #3 besides other
      things showed that even when we deactivate ZData objects, we are still
      keeping them as ghosts occupying memory and the same for IOBucket
      This all happens because there is no proper way to deactivate whole
      btree - including internal buckets objects. And since internal buckets
      are not deactivated, they stay in picklecache and thus hold a reference
      to ZData objects and ZData objects in turn, even if explicitly
      deactivated, stay in memory.
      We can fix this all via implementing whole-btree deactivation procedure.
      To do so we need to iterate over all btree buckets recursively, but
      unfortunately there is no BTree API to access/iterate btree's buckets.
      We can however still get reference to first top-level buckets via
      gc.get_referents(btree) and then scan buckets further without hacks.
      gc.get_referents(btree) is a hack, but
      - it works in O(1)  (we only get pointers from btree, not scanning all
        gcable objects and deducing them)
      - it works reliable if we filter out non-interesting objects.
      So in the end it works.
      Before the patch loading more and more ZBlk1 data with objgraph
      instrumentation was showing itself like
          #                                    Nobj        δ
          wendelin.bigfile.file_zodb.ZData     7168      +512
          BTrees.IOBTree.IOBucket               238       +17
          BTrees.IOBTree.IOBTree                 14        +1
      and after this patch we now have
          BTrees.IOBTree.IOBTree                 14        +1
      we cannot remove that "IOBTree + 1", since ZBlk1 is holding direct
      reference on it (via .chunktab) and we have to keep ZBlk1 live with
      ._v_zfile and ._v_zblk set for invalidation to work. "+1 IOBtree" is
      however small - 144 bytes per 2M (= 0.006%) so we can neglect that the
      same way we neglect keeping ZBlk1 staying live for each block.
  20. 14 Jul, 2016 1 commit
    • Kirill Smelkov's avatar
      bigfile/virtmem: usleep() needs unistd.h · d9d6409f
      Kirill Smelkov authored
      The following started to appear after recent gcc upgrade on my host:
      bigfile/virtmem.c: In function `vma_on_pagefault':
      bigfile/virtmem.c:696:9: warning: implicit declaration of function `usleep' [-Wimplicit-function-declaration]
               usleep(10000);  // XXX with 1000 uslepp still busywaits
  21. 01 Jul, 2016 1 commit
    • Kirill Smelkov's avatar
      bigfile/virtmem: Move _PyThreadState_Current_GET to compat_py2.h · 20115391
      Kirill Smelkov authored
      _PyThreadState_Current_GET() is a function to get current python thread
      state without asserting it is !NULL. It was added as part of d53271b9
      (bigfile/virtmem: Big Virtmem lock)
      We are going to adapt it to Python 3.5 (see next patch), so before doing
      so move it to our compatibility place.
      In the new place the name is _PyThreadState_UncheckedGet -- like such
      function is named in Python 3.5 (again, see next patch).
      Updates: #1
  22. 24 Jun, 2016 1 commit
    • Kirill Smelkov's avatar
      bigfile/pagemap: Fix non-leaf page iteration · ee9bcd00
      Kirill Smelkov authored
      Since the beginning of pagemap (45af76e6 "bigfile/pagemap: specialized
      {} uint64 -> void * mapping") we had a bug sitting in
      __pagemap_for_each_leaftab() (non-leaf iterating logic behind
      After entry to stack-down was found, we did not updated tailv[l]
      accordingly. Thus if there are non-adjacent entries an entry could be
      e.g. emitted many times:
           l 3  __down 0x7f79da1ee000
           tailv[4]: 0x7f79da1ee000
            -> tailv[4] 0x7f79da1ee000  __down 0x7f79da1ed000
           l 4  __down 0x7f79da1ed000
           tailv[5]: 0x7f79da1ed000
           h 5  l 5  leaftab: 0x7f79da1ed000      <--
            lvl 5  idx 169  page 0x55aa
          ok 9 - pagemap_for_each(0) == 21930
           l 5  __down (nil)
           tailv[4]: 0x7f79da1ee008
            -> tailv[4] 0x7f79da1ee008  __down 0x7f79da1ed000
           l 4  __down 0x7f79da1ed000
           tailv[5]: 0x7f79da1ed000
           h 5  l 5  leaftab: 0x7f79da1ed000      <--
            lvl 5  idx 169  page 0x55aa
          not ok 10 - pagemap_for_each(1) == 140724106500272
      And many-time-emitted entries are not only incorrect, but can also lead
      to not-handled segmentation faults in e.g. fileh_close():
          /* drop all pages (dirty or not) associated with this fileh */
          pagemap_for_each(page, &fileh->pagemap) {
              /* it's an error to close fileh to mapping of which an access is
               * currently being done in another thread */
              BUG_ON(page->state == PAGE_LOADING);
              list_del(&page->lru);                           <-- HERE
              bzero(page, sizeof(*page)); /* just in case */
      ( because after first bzero of a page, the page is all 0 bytes including
        page->lru{.next,.prev} so on the second time when the same page is
        emitted by pagemap_for_each, list_del(&page->lru) will try to set
        page->lru.next = ... which will segfault. )
      So fix it by properly updating tailv[l] while we scan/iterate current level.
      This applies only to non-leaf pagemap levels, as leaf level is scanned
      with separate loop in pagemap_for_each. That's why we probably did not
      noticed this earlier - up until now our usual workloads was to change
      data in adjacent batches and that means adjacent pages.
      Though today @Tyagov was playing with wendelin.core in some other way and
      it uncovered the bug.
  23. 20 Apr, 2016 1 commit
    • Kirill Smelkov's avatar
      bigfile/zodb: Don't write ZBlk to DB if it was not changed · 6773fda0
      Kirill Smelkov authored
      For ZBlk1 we already compare ZData content about whether it was changed
      compared to data already stored to DB, and do not store it twice if data
      is the same.
      However ZBlk itself is always marked as changed, if corresponding memory
      page was dirtied. This results in transactions like
      Trans #33915309 tid=03b6944919befeee time=2016-04-17 22:01:06.034237 offset=140320105842
          status=' ' user='...' description='...'
        # ... other parts, but no ZData here
        data #2 oid=000000000026fc4c size=79 class=wendelin.bigfile.file_zodb.ZBlk1
      where ZBlk1 is committed the same without necessity.
      NOTE we cannot avoid committing ZBlk in all cases, because it is used to signal
          other DB clients that a ZBlk needs to be invalidated and this way associated
          fileh pages are invalidated too.
          This cannot work via ZData, because ZData don't have back-pointer to
          ZBlk1 or to corresponding zfile.
  24. 15 Dec, 2015 5 commits
    • Kirill Smelkov's avatar
      bigfile/virtmem: Do loadblk() with virtmem lock released · f49c11a3
      Kirill Smelkov authored
      loadblk() calls are potentially slow and external code that serve the cal can
      take other locks in addition to virtmem lock taken by virtmem subsystem. If
      that "other locks" are also taken before external code calls e.g.
      fileh_invalidate_page() in different codepath a deadlock can happen, e.g.
            T1                  T2
            page-access         invalidation-from-server received
            V -> loadblk
                                Z   <- ClientStorage.invalidateTransaction()
            Z -> zeo.load
                                V   <- fileh_invalidate_page
      The solution to avoid deadlock is to call loadblk() with virtmem lock released
      and upon loadblk() completion recheck virtmem data structures carefully.
      To make that happen:
      - new page state is introduces:
          PAGE_LOADING                (file content loading is  in progress)
      - virtmem releases virt_lock before calling loadblk() when serving pagefault
      - because loading is now done with virtmem lock released, now:
      1. After loading completes we need to recheck fileh/vma data structures
         The recheck is done in full - vma_on_pagefault() just asks its driver (see
         VM_RETRY and VM_HANDLED codes) to retry handling the fault completely. This
         should work as the freshly loaded page was just inserted into fileh->pagemap
         and should be found there in the cache on next lookup.
         On the other hand this also works correctly, if there was concurrent change
         - e.g. vma was unmapped while we were loading the data - in that case the
         fault will be also processed correctly - but loaded data will stay in
         fileh->pagemap (and if not used will be evicted as not-needed
         eventually by RAM reclaim).
      2. Similar to retrying mechanism is used for cases when two threads
         concurrently access the same page and would both try to load corresponding
         block - only one thread issues the actual loadblk() and another waits for load
         to complete with polling and VM_RETRY.
      3. To correctly invalidate loading-in-progress pages another new page state
         is introduced:
          PAGE_LOADING_INVALIDATED    (file content loading was in progress
                                       while request to invalidate the page came in)
         which fileh_invalidate_page() uses to propagate invalidation message to
         loadblk() caller.
      4. Blocks loading can now happen in parallel with other block loading and
         other virtmem operations - e.g. invalidation. For such cases tests are added
         to test_thread.py
      5. virtmem lock now becomes just regular lock, instead of being previously
         For virtmem lock to be recursive was needed for cases, when code under
         loadblk() could trigger other virtmem calls, e.g. due to GC and calling
         another VMA dtor that would want to lock virtmem, but virtmem lock was
         already held.
         This is no longer needed.
      6. To catch double faults we now cannot use just on static variable
         in_on_pagefault. That variable thus becomes thread-local.
      7. Old test in test_thread to "test that access vs access don't overlap" no
         longer holds true - and is thus removed.
      /cc @Tyagov, @klaus
    • Kirill Smelkov's avatar
      bigfile/virtmem: Factor functionality to unlock/retake GIL into own functions · 0231a65d
      Kirill Smelkov authored
      Previously we were doing virt_lock() / virt_unlock() which automatically
      were making sure to unlock GIL before locking virtmem, and to restore
      GIL state to previous after virtmem lock happened. virt_unlock() was
      unlocking just the virtmem lock without touching GIL at all - that works
      because the running code would eventually release GIL as python
      regularly does so to allowing multiple threads to run.
      In the next patch however, we'll need to wait for in-progress-loading
      page to complete, and that wait has to be done with GIL released (so
      other python threads could run), and for doing so we'll need
      functionality to make sure GIL is unlocked and retake it back, not tied
      to virt_lock().
      So factor it out.
    • Kirill Smelkov's avatar
      bigfile/tests: move NotifyChannel to test_thread.py · 99cd1f03
      Kirill Smelkov authored
      NotifyChannel was introduced in c7c01ce4 (bigfile/zodb: ZODB.Connection
      can migrate between threads on close/open and we have to care) to test
      thread interaction specific to ZODB.
      We'll however need NotifyChannel to do more threading test of virtmem
      core, and this way the proper place for NotifyChannel is test_thread.py
      Move it.
    • Kirill Smelkov's avatar
      bigfile/virtmem: Remove obsolete XXX about locking · 81bf620c
      Kirill Smelkov authored
      Both comments are from the beginning - from 9a293c2d (bigfile/virtmem:
      Userspace Virtual Memory Manager) - but d53271b9 patch (bigfile/virtmem:
      Big Virtmem lock) missed to update them.
    • Kirill Smelkov's avatar
      bigfile: Plug memory leak in ramh_close() · 997ebacd
      Kirill Smelkov authored
      No one was freeing RAMH structure itself, and thus ASAN reports e.g.:
      ==15935==ERROR: LeakSanitizer: detected memory leaks
      Direct leak of 32 byte(s) in 1 object(s) allocated from:
          #0 0x7f29c89f1001 in __interceptor_calloc (/usr/lib/x86_64-linux-gnu/libasan.so.2+0x94001)
          #1 0x401da1 in zalloc include/wendelin/utils.h:65
          #2 0x408128 in shmfs_ramh_open bigfile/tests/../ram_shmfs.c:202
          #3 0x407611 in ramh_open bigfile/tests/../ram.c:81
          #4 0x402560 in fileh_open bigfile/tests/../virtmem.c:131
          #5 0x427ca1 in test_pagefault_savestate bigfile/tests/test_virtmem.c:1022
          #6 0x4281ba in main bigfile/tests/test_virtmem.c:1061
          #7 0x7f29c83b8b44 in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x21b44)
      NOTE similar leak remains open in ram_close(), but it is a bit involved
      to fix and the effort will be removed anyway after we switch to kernel
      virtual memory manager. Besides ramh are opened and closed all the time
      and ram only once.
  25. 05 Nov, 2015 1 commit
    • Kirill Smelkov's avatar
      bigfile/ram_shmfs: Warn users explicitly if fallocate() is not supported · e8c05a22
      Kirill Smelkov authored
      Currently wendelin.core does not work on e.g. Debian 7, because that
      distro has too old kernel without support for fallocate on tmpfs.
      But the diagnostics of failure is not clear and looks like just being
      out of memory:
          bigfile/tests/../virtmem.c:845 OOM      BUG!
      what happens in fact is that
          - virtmem tries to allocate a page -> calls shmfs_alloc_page(),
          - fallocate() fails with "operation not supported" error code
          - virtmem sees this as page allocation failure,
          - tries to reclaim pages,
          - but there are no allocated pages at all -> OOM
      Detect whether fallocate() error is operational error, or simply
      "fallocate not supported" and if latter, report to user. Now it looks
          bigfile/tests/../ram_shmfs.c:129 shmfs_alloc_page WARN: fallocate() not supported
          bigfile/tests/../virtmem.c:845 OOM      BUG!
      /cc @Tyagov
  26. 02 Oct, 2015 1 commit
    • Kirill Smelkov's avatar
      bigfile/zodb/tests: When testing check whole ZBlk content, not only [0] · 1895f0ed
      Kirill Smelkov authored
      Since 13c0c17c (bigfile/zodb: Format #1 which is optimized for small
      changes) each ZBlk is split into chunks, and the procedure to load/save
      blk data collects/disassembles the chunks.
      Previously in bigfile/zodb tests, we were testing by verifying only [0]
      element in each block, so this way most code for chunks
      collection/disassembling was not tested.
      Fix this aspect by making sure whole block content is as would-be
      expected in tests.
  27. 30 Sep, 2015 1 commit
    • Kirill Smelkov's avatar
      bigfile/zodb: Teach ZBlk1 not to waste memory on memory-page -> DB path · 1bf0cf31
      Kirill Smelkov authored
      ZBlk* objects are intermediate ZODB object in between data stored in
      ZODB and memory pages managed by virtmem. As such, after they do their
      job to either load data from DB to memory, or store from memory to DB,
      it is not needed to keep them alive with duplicate content thus only
      wasting memory.
      ZBlk0 cares about this detail via "deactivating" ._v_blkdata in
      loadblkdata() and __getstate__() prologues.
      ZBlk1 did the same for load path in loadblkdata() prologue, but for
      .__getstate__() it was not directly possible, because for ZBlk1 the
      state is IOBTree, not one non-persistent object, and thus it first needs
      to be processed by ZODB together with its subobjects on its way to
      storage and only then all they deactivated.
      So 13c0c17c (bigfile/zodb: Format #1 which is optimized for small
      changes) only put TODO for memory-page -> DB path about not wasting
      memory this way.
      But the problem is relatively easy to solve:
          - we can deactivate ZData objects (leaf objects in ZBlk1.chunktab
            btree) by hooking into ZData.__getstate__() prologue;
          - we also need to care to deactivate chunks right away, which
            setblkdata() loaded to compare .data and found them to be not
      This way we do not waste memory keeping intermediate ZData objects alive
      with the same content as memory page after commit.
      /cc @Tyagov
  28. 28 Sep, 2015 2 commits
    • Kirill Smelkov's avatar
      bigfile/zodb: Fix thinko in ZBlk1.setblkdata() · cdc6eda0
      Kirill Smelkov authored
      ZBlk1.setblkdata() has logic to detect CHUNKSIZE change, and if so
      recreate whole chunktab from scratch for simplicity. There was a thinko
      however - len(chunk.data) == CHUNKSIZE is ok and actually very often
      happens when data does not have zeroes.
      Because of this off-by-1 comparison mistake, ZData objects were
      constantly created and thrown out instead of being reused which led to
      fast ZODB growth.
      Fix it.
      /reported-by @Tyagov
    • Kirill Smelkov's avatar
      bigfile/zodb: Make ZBlk1 format the default · 9ae42085
      Kirill Smelkov authored
      Our current workloads are mostly a lot of small data changes and this is what
      ZBlk1 was created for. Yes it has larger overhead for accessing data, but we
      already painted the way how to handle this in 13c0c17c (bigfile/zodb: Format #1
      which is optimized for small changes) -> move data deduplication/management to
      server side.
      So be it ZBlk1 the default for now.
      /cc @Tyagov, @klaus