1. 17 Apr, 2018 1 commit
    • Kirill Smelkov's avatar
      tests: Force-close ZODB connections in teardown, that testing code forgot to explicitly close · 5a5ed2c7
      Kirill Smelkov authored
      If a test forgets to explicitly close ZODB connection it was using, this
      connection stays alive in transaction synchronizers (it is a weakset),
      and continues to be used on e.g. transaction.commit() when all
      synchronizers are invoked. This could lead to crashes like below when
      underlying ZODB storage was closed by test module teardown and testing
      moved on to another test module:
      
          $ WENDELIN_CORE_TEST_DB="<neo>" py.test  bigfile/tests/test_filezodb.py::test_bigfile_zblk1_zdata_reuse lib/tests/test_zodb.py
          ======= test session starts ========
          platform linux2 -- Python 2.7.14+, pytest-3.5.0, py-1.5.3, pluggy-0.6.0
          rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
          collected 2 items
      
          bigfile/tests/test_filezodb.py .                                     [ 50%]
          lib/tests/test_zodb.py F                                             [100%]
      
          ______ test_deactivate_btree _______
      
              def test_deactivate_btree():
                  root = dbopen()
                  # init btree with many leaf nodes
                  leafv = []
                  root['btree'] = B = IOBTree()
                  for i in range(10000):
                      B[i] = xi = XInt(i)
                      leafv.append(xi)
          >       transaction.commit()
      
          lib/tests/test_zodb.py:56:
          _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
          ../venv/z5/local/lib/python2.7/site-packages/transaction/_manager.py:131: in commit
              return self.get().commit()
          ../venv/z5/local/lib/python2.7/site-packages/transaction/_transaction.py:316: in commit
              self._synchronizers.map(lambda s: s.afterCompletion(self))
          ../venv/z5/local/lib/python2.7/site-packages/transaction/weakset.py:62: in map
              f(elt)
          ../venv/z5/local/lib/python2.7/site-packages/transaction/_transaction.py:316: in <lambda>
              self._synchronizers.map(lambda s: s.afterCompletion(self))
          ../venv/z5/local/lib/python2.7/site-packages/ZODB/Connection.py:757: in afterCompletion
              self.newTransaction(transaction, False)
          ../venv/z5/local/lib/python2.7/site-packages/ZODB/Connection.py:737: in newTransaction
              invalidated = self._storage.poll_invalidations()
          ../venv/z5/local/lib/python2.7/site-packages/ZODB/mvccadapter.py:131: in poll_invalidations
              self._start = p64(u64(self._storage.lastTransaction()) + 1)
          _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
      
          self = <neo.client.Storage.Storage object at 0x7ffa1be8d410>
      
              def lastTransaction(self):
                  # Used in ZODB unit tests
          >       return self.app.last_tid
          E       AttributeError: 'NoneType' object has no attribute 'last_tid'
      
          ../../neo/src/lab.nexedi.com/kirr/neo/neo/client/Storage.py:181: AttributeError
      
      where NEO's Storage.app is None because the storage was closed.
      
      ----
      
      To avoid such kind of failures make sure TestDB.teardown() always closes
      all ZODB connections that were ever opened via TestDB.dbopen().
      
      Add a warning about such force-closing with information about corresponding
      connection and code place that created it, so that it is easy to
      understand which test needs a fix.
      
      /suggested-by @jm
      5a5ed2c7
  2. 16 Apr, 2018 4 commits
    • Kirill Smelkov's avatar
      wendelin.core v0.12 · 72df6eb6
      Kirill Smelkov authored
      72df6eb6
    • Kirill Smelkov's avatar
      tox: v↑ everything · 0dbf3c44
      Kirill Smelkov authored
      - start testing NEO with ZODB4 and ZODB5. ZODB4-*-NEO works while
        ZODB5-*-NEO currently fails [1].
      - update NumPy to latest releases.
      - update Python 3 to latest releases.
      0dbf3c44
    • Kirill Smelkov's avatar
      tests: Teach to run with ZEO5 · 808b59b7
      Kirill Smelkov authored
      Since 7fc4ec66 (tests: Allow to test with ZEO & NEO ZODB storages) we
      can run the tests with either FileStorage, ZEO or NEO. But ZEO test
      adapter started to fail with ZEO5:
      
      	self = <wendelin.lib.testing.TestDB_ZEO object at 0x7f1feb5091d0>
      
      	    def setup(self):
      	        port  = self.zeo_forker.get_port()
      	        zconf = self.zeo_forker.ZEOConfig(('', port))
      	        self.addr, self.adminaddr, self.pid, self.path = \
      	>               self.zeo_forker.start_zeo_server(zeo_conf=zconf, port=port)
      	E       ValueError: need more than 2 values to unpack
      
      This is because in ZEO5 forker.start_zeo_server() was reworked to return only
      addr and stop closure instead of returning all details and relying on caller to
      implement stop itself.
      
      Adapt the test to detect ZEO5 and use new calling convention.
      808b59b7
    • Kirill Smelkov's avatar
      tox/ZODB3: Limit ZConfig < 3.2.0 · c96daa2f
      Kirill Smelkov authored
      With ZConfig==3.2.0 (released 2017-06-22) ZEO started to get file names
      as unicode, which eventually breaks on assert in ZODB3.FileStorage code:
      
          Traceback (most recent call last):
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZEO/tests/zeoserver.py", line 215, in <module>
              main()
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZEO/tests/zeoserver.py", line 188, in main
              server = ZEO.runzeo.create_server({"1": storage}, zo)
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZEO/runzeo.py", line 366, in create_server
              auth_realm = options.auth_realm,
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZEO/StorageServer.py", line 890, in __init__
              self._setup_invq(name, storage)
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZEO/StorageServer.py", line 928, in _setup_invq
              self.invq[name] = list(lastInvalidations(self.invq_bound))
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZODB/FileStorage/FileStorage.py", line 1238, in lastInvalidations
              for trans in FileIterator(self._file_name, pos=pos)]
            File "/home/kirr/src/wendelin/r/work/venv/local/lib/python2.7/site-packages/ZODB/FileStorage/FileStorage.py", line 1640, in __init__
              assert isinstance(filename, str), `filename`
          AssertionError: u'/home/kirr/src/wendelin/r/work/wendelin.core/Data.fs'
      
      -> So for ZODB3 pin ZConfig to ZConfig <3.2.0 which is known to work with ZODB3.
      c96daa2f
  3. 12 Apr, 2018 1 commit
    • Kirill Smelkov's avatar
      bigarray: ArrayRef utility · e9d61a89
      Kirill Smelkov authored
      ArrayRef is a reference to NumPy array.
      
      The reference is represented by root array object and instructions how to
      create original array as some view of the root.
      
      Such 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.
      
      Use `ArrayRef(array)` to create reference to an ndarray.
      
      Use `.deref()` to convert ArrayRef to pointed array object.
      
      NOTE
      
      don't send ArrayRef unconditionally - for example when array object is
      small regular ndarray with also regular, but big, root ndarray, sending
      ArrayRef will send whole data for root object, not for small leaf.
      
      Sending ArrayRef only makes sense when root object is known to be already
      persisted by other means, for example something like below in ZODB context:
      
      ```python
        aref = ArrayRef(a)
        if isinstance(aref.root, Persistent):
            send aref
        else:
            send a 
      ```
      
      Please see individual patches for more details.
      
      /reviewed-on nexedi/wendelin.core!6
      e9d61a89
  4. 02 Apr, 2018 2 commits
    • Kirill Smelkov's avatar
      bigarray: ArrayRef support for BigArray · 450ad804
      Kirill Smelkov authored
      Rationale
      ---------
      
      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.
      
      Implementation
      --------------
      
      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
        BigArray.
      
      - 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.
      450ad804
    • Kirill Smelkov's avatar
      bigarray: Add ArrayRef utility · d53371b6
      Kirill Smelkov authored
      ArrayRef is a tool to find out for a NumPy array its top-level root
      parent and remember instructions how to recreate original array from
      the root. For example if
      
      	root = arange(1E7)
      	z = root[1000:2000]
      	a = z[10:20]
      
      `ArrayRef(a)` will find out that the root array for `a` is `root` and
      that `a` occupies 1010:1020 bytes in it. The vice versa operation is
      also possible, for example given
      
      	aref = ArrayRef(a)
      
      it is possible to restore original `a` from `aref`:
      
      	a_ = aref.deref()
      	assert array_equal(a_, a)
      
      the restoration works without copying by creating appropriate view of
      root.
      
      ArrayRef should work reliably for arrays of arbitrary dimensions,
      strides etc - even fancy arrays created via stride tricks such as arrays
      whose elements overlap each other should be supported.
      
      This patch adds ArrayRef with support for regular ndarrays only.
      
      The next patch will add ArrayRef support for BigArray and description
      for ArrayRef rationale.
      d53371b6
  5. 21 Feb, 2018 1 commit
  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>.
      c3cc8a99
  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
      allocating.
      
      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:
      
      	https://lab.nexedi.com/nexedi/wendelin.core/blob/f11386a4/bigfile/ram_shmfs.c#L125
      
      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
      3cfc2728
  8. 06 Dec, 2017 1 commit
    • Kirill Smelkov's avatar
      py.bench: Rework output to match Go benchmarking format · 51f252d4
      Kirill Smelkov authored
      Rework py.bench output to match output of Go benchmarking[1] so that go
      tools like benchstat & friends could be used to analyze and compare the
      timings.
      
      Before patch:
      
      ============================= test session starts ==============================
      platform linux2 -- Python 2.7.14, pytest-3.3.2.dev2+g88f2cc9b.d20171206, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 11 items
      
      pymod: bigfile/tests/bench_0virtmem.py
      bench_file_mmapread_hole	0.21  (0.39 0.22 0.21)
      bench_file_read_hole    	0.24  (0.24 0.24 0.24)
      bench_file_readbig_hole 	0.30  (0.30 0.31 0.31)
      bench_bigf_read_hole    	0.44  (0.44 0.45 0.44)
      bench_file_mmapwrite0   	0.13  (0.13 0.13 0.13)
      bench_file_write55      	0.08  (0.08 0.08 0.08)
      bench_bigf_writeff      	0.47  (0.47 0.48 0.47)
      bench_file_mmapread     	0.22  (0.22 0.22 0.22)
      bench_file_read         	0.25  (0.25 0.25 0.26)
      bench_file_readbig      	0.31  (0.31 0.31 0.31)
      bench_bigf_read         	0.44  (0.45 0.44 0.44)
      
      ========================== 11 passed in 12.92 seconds ==========================
      
      After patch:
      
      ============================= test session starts ==============================
      platform linux2 -- Python 2.7.14, pytest-3.3.2.dev2+g88f2cc9b.d20171206, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 11 items
      
      pymod: bigfile/tests/bench_0virtmem.py
      Benchmarkfile_mmapread_hole	1	385839.939 µs/op
      Benchmarkfile_mmapread_hole	1	219214.916 µs/op
      Benchmarkfile_mmapread_hole	1	210209.131 µs/op
      Benchmarkfile_read_hole    	1	238974.094 µs/op
      Benchmarkfile_read_hole    	1	237294.197 µs/op
      Benchmarkfile_read_hole    	1	238043.070 µs/op
      Benchmarkfile_readbig_hole 	1	301330.090 µs/op
      Benchmarkfile_readbig_hole 	1	301767.111 µs/op
      Benchmarkfile_readbig_hole 	1	301135.063 µs/op
      Benchmarkbigf_read_hole    	1	434718.132 µs/op
      Benchmarkbigf_read_hole    	1	435019.970 µs/op
      Benchmarkbigf_read_hole    	1	434729.099 µs/op
      Benchmarkfile_mmapwrite0   	1	126471.996 µs/op
      Benchmarkfile_mmapwrite0   	1	125886.917 µs/op
      Benchmarkfile_mmapwrite0   	1	125730.038 µs/op
      Benchmarkfile_write55      	1	86760.044 µs/op
      Benchmarkfile_write55      	1	87507.010 µs/op
      Benchmarkfile_write55      	1	87735.891 µs/op
      Benchmarkbigf_writeff      	1	448369.980 µs/op
      Benchmarkbigf_writeff      	1	448238.850 µs/op
      Benchmarkbigf_writeff      	1	447322.845 µs/op
      Benchmarkfile_mmapread     	1	207049.131 µs/op
      Benchmarkfile_mmapread     	1	207813.978 µs/op
      Benchmarkfile_mmapread     	1	210857.868 µs/op
      Benchmarkfile_read         	1	238364.935 µs/op
      Benchmarkfile_read         	1	236908.913 µs/op
      Benchmarkfile_read         	1	238602.161 µs/op
      Benchmarkfile_readbig      	1	303429.842 µs/op
      Benchmarkfile_readbig      	1	302191.973 µs/op
      Benchmarkfile_readbig      	1	304115.057 µs/op
      Benchmarkbigf_read         	1	435079.098 µs/op
      Benchmarkbigf_read         	1	434193.850 µs/op
      Benchmarkbigf_read         	1	434813.976 µs/op
      
      ========================== 11 passed in 12.54 seconds ==========================
      
      benchstat of new output:
      
      	name                µs/op
      	file_mmapread_hole   272k ±42%
      	file_read_hole       238k ± 0%
      	file_readbig_hole    301k ± 0%
      	bigf_read_hole       435k ± 0%
      	file_mmapwrite0      126k ± 0%
      	file_write55        87.3k ± 1%
      	bigf_writeff         448k ± 0%
      	file_mmapread        209k ± 1%
      	file_read            238k ± 0%
      	file_readbig         303k ± 0%
      	bigf_read            435k ± 0%
      
      --------
      
      Not only the output format is reworked here, but also b fixture is added
      to BenchPlugin so that functions which accept it as arg, can run the
      benchmark b.N times, can stop/restart timer etc - similar to
      https://golang.org/pkg/testing/#B . If a bench_* func does not accept b,
      B is still created, but b.N is assumed to be always 1.
      
      The first benchmark which actually uses b will come in the next patch.
      
      [1] https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md
      51f252d4
  9. 05 Dec, 2017 1 commit
  10. 04 Dec, 2017 2 commits
    • Kirill Smelkov's avatar
      py.bench: Less noisy output · 074ce24d
      Kirill Smelkov authored
      For several benchmarks in a python module instead of always printing
      whole benchmark function path (nodeid in pytest speak), first print
      
      	pymod: modulename
      
      header, and then only function names.
      
      Before patch:
      
      ============================= test session starts ==============================
      platform linux2 -- Python 2.7.14, pytest-3.3.0, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 14 items
      
      bigfile/tests/bench_0virtmem.py::bench_file_mmapread_hole 	0.22  (0.39 0.22 0.22)
      bigfile/tests/bench_0virtmem.py::bench_file_read_hole    	0.24  (0.24 0.24 0.24)
      bigfile/tests/bench_0virtmem.py::bench_file_readbig_hole 	0.30  (0.30 0.30 0.30)
      bigfile/tests/bench_0virtmem.py::bench_bigf_read_hole    	0.43  (0.43 0.43 0.43)
      bigfile/tests/bench_0virtmem.py::bench_file_mmapwrite0   	0.12  (0.13 0.12 0.13)
      bigfile/tests/bench_0virtmem.py::bench_file_write55      	0.08  (0.08 0.08 0.08)
      bigfile/tests/bench_0virtmem.py::bench_bigf_writeff      	0.45  (0.45 0.45 0.45)
      bigfile/tests/bench_0virtmem.py::bench_file_mmapread     	0.21  (0.21 0.21 0.22)
      bigfile/tests/bench_0virtmem.py::bench_file_read         	0.23  (0.23 0.24 0.24)
      bigfile/tests/bench_0virtmem.py::bench_file_readbig      	0.30  (0.30 0.30 0.30)
      bigfile/tests/bench_0virtmem.py::bench_bigf_read         	0.43  (0.44 0.43 0.44)
      bigfile/tests/bench_1filezodb.py::bench_bigz_readhole    	0.42  (0.42 0.43 0.43)
      bigfile/tests/bench_1filezodb.py::bench_bigz_writeff     	0.84  (0.84 1.25 1.25)
      bigfile/tests/bench_1filezodb.py::bench_bigz_read        	0.60  (0.64 0.60 0.60)
      ========================== 14 passed in 21.78 seconds ==========================
      
      After patch:
      
      ============================= test session starts ==============================
      platform linux2 -- Python 2.7.14, pytest-3.3.0, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 14 items
      
      pymod: bigfile/tests/bench_0virtmem.py
      bench_file_mmapread_hole	0.21  (0.39 0.22 0.21)
      bench_file_read_hole    	0.24  (0.24 0.24 0.24)
      bench_file_readbig_hole 	0.30  (0.30 0.30 0.30)
      bench_bigf_read_hole    	0.43  (0.43 0.43 0.43)
      bench_file_mmapwrite0   	0.12  (0.13 0.12 0.13)
      bench_file_write55      	0.08  (0.08 0.08 0.08)
      bench_bigf_writeff      	0.45  (0.52 0.51 0.45)
      bench_file_mmapread     	0.21  (0.21 0.21 0.21)
      bench_file_read         	0.24  (0.24 0.24 0.24)
      bench_file_readbig      	0.30  (0.30 0.30 0.30)
      bench_bigf_read         	0.43  (0.43 0.43 0.43)
      
      pymod: bigfile/tests/bench_1filezodb.py
      bench_bigz_readhole     	0.43  (0.43 0.43 0.43)
      bench_bigz_writeff      	0.83  (0.83 1.20 1.21)
      bench_bigz_read         	0.60  (0.65 0.60 0.60)
      
      ========================== 14 passed in 21.80 seconds ==========================
      
      The `key: value` header is compatible with Go benchmark format[1] and
      further we'll be trying to teach py.bench to output results in that
      format so that Go benchmarking tools like benchstat and perf.golang.org
      could be used for free.
      
      [1] https://github.com/golang/proposal/blob/master/design/14313-benchmark-format.md
      074ce24d
    • Kirill Smelkov's avatar
      py.bench: Fix output reporting · ed13c3f9
      Kirill Smelkov authored
      Upstream pytest changed TerminalReporter._locationline() signature from
      
      	def _locationline(self, collect_fspath, fspath, lineno, domain):
      
      to
      
      	def _locationline(self, nodeid, fspath, lineno, domain):
      
      https://github.com/pytest-dev/pytest/commit/d73e6899 .
      
      This way without adjusting py.bench was reporting just filenames instead
      of benchmark names, e.g. this way:
      
      (z-dev) kirr@deco:~/src/wendelin/wendelin.core$ ./t/py.bench bigfile/tests/bench_0virtmem.py
      =============================================================== test session starts ===============================================================
      platform linux2 -- Python 2.7.14, pytest-3.3.0, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 11 items
      
      bigfile/tests/bench_0virtmem.py                                 0.21  (0.38 0.22 0.21)
      bigfile/tests/bench_0virtmem.py                                 0.23  (0.23 0.24 0.23)
      bigfile/tests/bench_0virtmem.py                                 0.30  (0.30 0.30 0.30)
      bigfile/tests/bench_0virtmem.py                                 0.43  (0.43 0.43 0.43)
      bigfile/tests/bench_0virtmem.py                                 0.12  (0.12 0.12 0.12)
      bigfile/tests/bench_0virtmem.py                                 0.08  (0.08 0.08 0.08)
      bigfile/tests/bench_0virtmem.py                                 0.44  (0.44 0.44 0.44)
      bigfile/tests/bench_0virtmem.py                                 0.20  (0.20 0.20 0.21)
      bigfile/tests/bench_0virtmem.py                                 0.24  (0.24 0.24 0.24)
      bigfile/tests/bench_0virtmem.py                                 0.30  (0.30 0.30 0.30)
      bigfile/tests/bench_0virtmem.py                                 0.43  (0.43 0.43 0.43)
      
      =========================================================== 11 passed in 12.39 seconds ============================================================
      
      Fix it.
      ed13c3f9
  11. 02 Dec, 2017 1 commit
    • Kirill Smelkov's avatar
      py.bench: Fix report.bench_time collect · fc08766d
      Kirill Smelkov authored
      In pytest3 pytest_runtest_call hook is always run as multicall and there
      is no way to control to disable it and force e.g. 'firstresult'
      behaviour. This way call.result in pytest_runtest_makereport is always a
      list of returned results, not the first result itself.
      
      Adjust the code in pytest_runtest_makereport to emulate 'firstresult'
      behaviour manually.
      
      Without the patch with recent pytest py.bench was crashing like this:
      
      ---- 8< ----
      (z-dev) kirr@link:~/src/wendelin/wendelin.core$ make bench
      python setup.py ll_build_ext --inplace
      running ll_build_ext
      copying build/lib.linux-x86_64-2.7/wendelin/bigfile/_bigfile.so -> bigfile
      python t/py.bench --ignore=3rdparty --ignore=build --ignore=t
      ============================================================= test session starts ==============================================================
      platform linux2 -- Python 2.7.14, pytest-3.3.0, py-1.5.2, pluggy-0.6.0
      rootdir: /home/kirr/src/wendelin/wendelin.core, inifile:
      collected 14 items
      
      bigfile/tests/bench_0virtmem.py                          	INTERNALERROR> Traceback (most recent call last):
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/main.py", line 103, in wrap_session
      INTERNALERROR>     session.exitstatus = doit(config, session) or 0
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/main.py", line 141, in _main
      INTERNALERROR>     config.hook.pytest_runtestloop(session=session)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 617, in __call__
      INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 222, in _hookexec
      INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 216, in <lambda>
      INTERNALERROR>     firstresult=hook.spec_opts.get('firstresult'),
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 201, in _multicall
      INTERNALERROR>     return outcome.get_result()
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 77, in get_result
      INTERNALERROR>     _reraise(*ex)  # noqa
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 180, in _multicall
      INTERNALERROR>     res = hook_impl.function(*args)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/main.py", line 164, in pytest_runtestloop
      INTERNALERROR>     item.config.hook.pytest_runtest_protocol(item=item, nextitem=nextitem)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 617, in __call__
      INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 222, in _hookexec
      INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 216, in <lambda>
      INTERNALERROR>     firstresult=hook.spec_opts.get('firstresult'),
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 201, in _multicall
      INTERNALERROR>     return outcome.get_result()
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 77, in get_result
      INTERNALERROR>     _reraise(*ex)  # noqa
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 180, in _multicall
      INTERNALERROR>     res = hook_impl.function(*args)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/runner.py", line 63, in pytest_runtest_protocol
      INTERNALERROR>     runtestprotocol(item, nextitem=nextitem)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/runner.py", line 77, in runtestprotocol
      INTERNALERROR>     reports.append(call_and_report(item, "call", log))
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/_pytest/runner.py", line 161, in call_and_report
      INTERNALERROR>     hook.pytest_runtest_logreport(report=report)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 617, in __call__
      INTERNALERROR>     return self._hookexec(self, self._nonwrappers + self._wrappers, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 222, in _hookexec
      INTERNALERROR>     return self._inner_hookexec(hook, methods, kwargs)
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/__init__.py", line 216, in <lambda>
      INTERNALERROR>     firstresult=hook.spec_opts.get('firstresult'),
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 201, in _multicall
      INTERNALERROR>     return outcome.get_result()
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 77, in get_result
      INTERNALERROR>     _reraise(*ex)  # noqa
      INTERNALERROR>   File "/home/kirr/src/wendelin/venv/z-dev/local/lib/python2.7/site-packages/pluggy/callers.py", line 180, in _multicall
      INTERNALERROR>     res = hook_impl.function(*args)
      INTERNALERROR>   File "t/py.bench", line 165, in pytest_runtest_logreport
      INTERNALERROR>     self._tw.write('%.2f' % min(report.bench_times))
      INTERNALERROR> TypeError: float argument required, not list
      fc08766d
  12. 01 Dec, 2017 2 commits
    • Kirill Smelkov's avatar
      py.bench: Don't forget to clear config's inicache after tweaking config.inicfg · 5a1ed45a
      Kirill Smelkov authored
      If py.test machinery access any of the keys we change before we tweak
      them - their values will be put to config._inicache and our tweaking
      won't essentially have the effect.
      
      It used to be working without explicit cache invalidation with older
      pytests, but as of e.g. py.test 3.2.1 it does not work without explicit
      cache clearing because e.g. 'python_files' is somehow accessed before
      and is set to std 'test_*.py' + friends in the cache and this way
      py.bench then does not collect benchmarks from bench_*.py files.
      
      Fix it by making sure inicache is invalidated after our inicfg tweak.
      5a1ed45a
    • Kirill Smelkov's avatar
      9e471114
  13. 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.
      f11386a4
  14. 04 Sep, 2017 1 commit
    • Kirill Smelkov's avatar
      demo_zbigarray: Allow to specify work size as option · 38fbc83c
      Kirill Smelkov authored
      Since the beginning (1ee72371 "Demo program that shows how to work with
      ZBigArrays bigger than RAM in size") the work size was 2*RAM.
      
      However sometimes for demonstration purposes it could be handy to set it
      to be even more (e.g.  16*RAM) or vise versa - to something lower.
      
      So add command line option to do so instead of manually patching sources
      every time.
      38fbc83c
  15. 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:
              pass
      
          # call f on __del__
          class DelCall:
              def __init__(self, f):
                  self.f = f
      
              def __del__(self):
                  self.f()
      
          # _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):
                  mkgarbage(1000)
                  print '> %s' % (gc.get_count(),)
                  n = gc.collect()
                  print '< %s' % (gc.get_count(),)
      
          main()
          ---- 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:
      
          https://github.com/python/cpython/blob/883520a8/Python/ceval.c#L3717
      
      Since this took some time to find, more diagnostics is also added before
      BUG_ONs corresponding to finding unclean exception state.
      4228d8b6
  16. 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:
              try:
                  f()
              except:
                  pass
      
      t1, t2 = Thread(target=T1), Thread(target=T2)
      t1.start(); t2.start()
      ---- 8< ----
      
      ---- 8< ----
      kirr@deco:~/tmp/trashme/t$ ./excthreads.py
      T1
      T2
      
      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.
      3804cc39
  17. 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.
      87bf4908
  18. 28 Mar, 2017 1 commit
  19. 24 Mar, 2017 1 commit
  20. 16 Mar, 2017 2 commits
  21. 10 Mar, 2017 1 commit
    • Kirill Smelkov's avatar
      bigarray: Explicitly reject dtypes with object inside · e44bd761
      Kirill Smelkov authored
      From time to time people keep trying to use wendelin.core with
      dtype=object arrays and get segfaults without anything in logs or
      whatever else.
      
      Wendelin.core does not support it, because in case of dtype=object elements are
      really pointers and data for each object is stored in separate place in RAM
      with different per-object size.
      
      As we are memory-mapping arrays this won't work. It also does not
      essentially work for numpy.memmap for the same reason:
      
          (z4+numpy) kirr@mini:~/src/wendelin$ dd if=/dev/zero of=zero.dat bs=128 count=1
          1+0 records in
          1+0 records out
          128 bytes copied, 0.000209873 s, 610 kB/s
          (z4+numpy) kirr@mini:~/src/wendelin$ dd if=/dev/urandom of=random.dat bs=128 count=1
          1+0 records in
          1+0 records out
          128 bytes copied, 0.000225726 s, 567 kB/s
          (z4+numpy) kirr@mini:~/src/wendelin$ ipython
          ...
      
          In [1]: import numpy as np
      
          In [2]: np.memmap('zero.dat', dtype=np.object)
          Out[2]:
          memmap([None, None, None, None, None, None, None, None, None, None, None,
                 None, None, None, None, None], dtype=object)
      
          In [3]: np.memmap('random.dat', dtype=np.object)
          Out[3]: Segmentation fault
      
      So let's clarify this to users via explicitly raising exception when
      BigArray with non-appropriate dtype is trying to be created with
      descriptive explanation also logged.
      
      /reviewed-on !4
      e44bd761
  22. 17 Jan, 2017 4 commits
  23. 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 nexedi/wendelin.core#7
      024c246c
    • 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
      them.
      
      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.
      3a8e1beb
    • 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!
      61b18a40
    • 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.
      f01b27d2
  24. 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.
      ca7c1b6d
    • 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!
      20b41a5a
  25. 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:
      
      After
      
      	_, _, exc_traceback = sys.exc_info()
      
      there is a reference loop created:
      
      	exc_traceback
      	  |        ^
      	  |        |
      	  v     .f_localsplus
      	 frame
      
      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
      away.
      
      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: nexedi/wendelin.core#7
      9aa6a5d7
    • 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.
      b4c269eb