- 12 Aug, 2015 4 commits
-
-
Kirill Smelkov authored
Intro ----- ZODB maintains pool of opened-to-DB connections. For each request Zope opens 1 connection and, after request handling is done, returns the connection back to ZODB pool (via Connection.close()). The same connection will be opened again for handling some future next request at some future time. This next open can happen in different-from-first request worker thread. TransactionManager (as accessed by transaction.{get,commit,abort,...}) is thread-local, that is e.g. transaction.get() returns different transaction for threads T1 and T2. When _ZBigFileH hooks into txn_manager to get a chance to run its .beforeCompletion() when transaction.commit() is run, it hooks into _current_ _thread_ transaction manager. Without unhooking on connection close, and circumstances where connection migrates to different thread this can lead to dissynchronization between ZBigFileH managing fileh pages and Connection with ZODB objects. And even to data corruption, e.g. T1 T2 open zarray[0] = 11 commit close open # opens connection as closed in T1 open zarray[0] = 21 commit abort close close Here zarray[0]=21 _will_ be committed by T1 as part of T1 transaction - because when T1 does commit .beforeCompletion() for zarray is invoked, sees there is dirty data and propagate changes to zodb objects in connection for T2, joins connection for T2 into txn for T1, and then txn for t1 when doing two-phase-commit stores modified objects to DB -> oops. ---------------------------------------- To prevent such dissynchronization _ZBigFileH needs to be a DataManager which works in sync with the connection it was initially created under - on connection close, unregister from transaction_manager, and on connection open, register to transaction manager in current, possibly different, thread context. Then there won't be incorrect beforeCompletion() notification and corruption. This issue, besides possible data corruption, was probably also exposing itself via following ways we've seen in production (everywhere connection was migrated from T1 to T2): 1. Exception ZODB.POSException.ConnectionStateError: ConnectionStateError('Cannot close a connection joined to a transaction',) in <bound method Cleanup.__del__ of <App.ZApplication.Cleanup instance at 0x7f10f4bab050>> ignored T1 T2 modify zarray commit/abort # does not join zarray to T2.txn, # because .beforeCompletion() is # registered in T1.txn_manager commit # T1 invokes .beforeCompletion() ... # beforeCompletion() joins ZBigFileH and zarray._p_jar (=T2.conn) to T1.txn ... # commit is going on in progress ... ... close # T2 thinks request handling is done and ... # and closes connection. But T2.conn is ... # still joined to T1.txn 2. Traceback (most recent call last): File ".../wendelin/bigfile/file_zodb.py", line 121, in storeblk def storeblk(self, blk, buf): return self.zself.storeblk(blk, buf) File ".../wendelin/bigfile/file_zodb.py", line 220, in storeblk zblk._v_blkdata = bytes(buf) # FIXME does memcpy File ".../ZODB/Connection.py", line 857, in setstate raise ConnectionStateError(msg) ZODB.POSException.ConnectionStateError: Shouldn't load state for 0x1f23a5 when the connection is closed Similar to "1", but close in T2 happens sooner, so that when T1 does the commit and tries to store object to database, Connection refuses to do the store: T1 T2 modify zarray commit/abort commit ... close ... ... . obj.store() ... ... 3. Traceback (most recent call last): File ".../wendelin/bigfile/file_zodb.py", line 121, in storeblk def storeblk(self, blk, buf): return self.zself.storeblk(blk, buf) File ".../wendelin/bigfile/file_zodb.py", line 221, in storeblk zblk._p_changed = True # if zblk was already in DB: _p_state -> CHANGED File ".../ZODB/Connection.py", line 979, in register self._register(obj) File ".../ZODB/Connection.py", line 989, in _register self.transaction_manager.get().join(self) File ".../transaction/_transaction.py", line 220, in join Status.ACTIVE, Status.DOOMED, self.status)) ValueError: expected txn status 'Active' or 'Doomed', but it's 'Committing' ( storeblk() does zblk._p_changed -> Connection.register(zblk) -> txn.join() but txn is already committing IOW storeblk() was invoked with txn.state being already 'Committing' ) T1 T2 modify obj # this way T2.conn joins T2.txn modify zarray commit # T1 invokes .beforeCompletion() ... # beforeCompletion() joins only _ZBigFileH to T1.txn ... # (because T2.conn is already marked as joined) ... ... commit/abort # T2 does commit/abort - this touches only T2.conn, not ZBigFileH ... # in particular T2.conn is now reset to be not joined ... . tpc_begin # actual active commit phase of T1 was somehow delayed a bit . tpc_commit # when changes from RAM propagate to ZODB objects associated . storeblk # connection (= T2.conn !) is notified again, . zblk = ... # wants to join txn for it thinks its transaction_manager, # which when called from under T1 returns *T1* transaction manager for # which T1.txn is already in state='Committing' 4. Empty transaction committed to NEO ( different from doing just transaction.commit() without changing any data - a connection was joined to txn, but set of modified object turned out to be empty ) This is probably a race in Connection._register when both T1 and T2 go to it at the same time: https://github.com/zopefoundation/ZODB/blob/3.10/src/ZODB/Connection.py#L988 def _register(self, obj=None): if self._needs_to_join: self.transaction_manager.get().join(self) self._needs_to_join = False T1 T2 modify zarray commit ... .beforeCompletion modify obj . if T2.conn.needs_join if T2.conn.needs_join # race here . T2.conn.join(T1.txn) T2.conn.join(T2.txn) # as a result T2.conn joins both T1.txn and T2.txn . commit finishes # T2.conn registered-for-commit object list is now empty commit tpc_begin storage.tpc_begin tpc_commit # no object stored, because for-commit-list is empty /cc @jm, @klaus, @Tyagov, @vpelletier
-
Kirill Smelkov authored
ZODB.Connection has support for calling callbacks on .close() but not on .open() . We'll need to hook into both Connection open/close process in the next patch (for _ZBigFileH to stay in sync with Connection state). NOTE on-open callbacks are setup once and fire many times on every open, on-close callbacks are setup once and fire only once on next close. The reason for this is that on-close callbacks are useful for scheduling current connection cleanup, after its processing is done. But on-open callback is for future connection usage, which is generally not related to current connection. /cc @jm, @vpelletier
-
Kirill Smelkov authored
-
Kirill Smelkov authored
( without dbclose, next test will not be able to open database - will timeout on open on waiting for FileStorage lock )
-
- 09 Aug, 2015 1 commit
-
-
Kirill Smelkov authored
Previously we were limited to printing traceback starting down from just storeblk() via explicit PyErr_PrintEx() - because pybuf was attached to memory which could go away right after return from C function - so we had to destroy that object for sure, not letting any traceback to hold a reference to it. This turned out to be too limiting and not showing full context where errors happen. So do the following trick: before returning, reattach pybuf to empty region at NULL, and this way we don't need to worry about pybuf pointing to memory which can go away -> thus instead of printing exception locally - just return it the usual way it is done with C api in Python. NOTE In contrast to PyMemoryViewObject, PyBufferObject definition is not public, so to support Python2 - had to copy its definition to PY2 compat header. NOTE2 loadblk() is not touched - the loading is done from sighandler context, which simulates as if it work in separate python thread, so it is leaved as is for now.
-
- 06 Aug, 2015 8 commits
-
-
Kirill Smelkov authored
At present several threads running can corrupt internal virtmem datastructures (e.g. ram->lru_list, fileh->pagemap, etc). This can happen even if we have zope instances only with 1 worker thread - because there are other "system" thread, and python garbage collection can trigger at any thread, so if a virtmem object, e.g. VMA or FileH was there sitting at GC queue to be collected, their collection, and thus e.g. vma_unmap() and fileh_close() will be called from different-from-worker thread. Because of that virtmem just has to be aware of threads not to allow internal datastructure corruption. On the other hand, the idea of introducing userspace virtual memory manager turned out to be not so good from performance and complexity point of view, and thus the plan is to try to move it back into the kernel. This way it does not make sense to do a well-optimised locking implementation for userspace version. So we do just a simple single "protect-all" big lock for virtmem. Of a particular note is interaction with Python's GIL - any long-lived lock has to be taken with GIL released, because else it can deadlock: t1 t2 G V G !G V G so we introduce helpers to make sure the GIL is not taken, and to retake it back if we were holding it initially. Those helpers (py_gil_ensure_unlocked / py_gil_retake_if_waslocked) are symmetrical opposites to what Python provides to make sure the GIL is locked (via PyGILState_Ensure / PyGILState_Release). Otherwise, the patch is more-or-less straightforward application for one-big-lock to protect everything idea.
-
Kirill Smelkov authored
Mutex lock/unlock should not fail if mutex was correctly initialized/used.
-
Kirill Smelkov authored
And specifically that GC'ed object __del__ calls into virtmem (vma_dealloc and fileh_dealloc) again. NOTE not sure it is a good idea to do GC from under sighandle, but currently it happens in practice, because we did not cared to protect against it.
-
Kirill Smelkov authored
We factored out SIGSEGV block/restore from fileh_dirty_writeout() to all functions in cb7a7055 (bigfile/virtmem: Block/restore SIGSEGV in non-pagefault-handling function). The restoration however just sets whole thread sigmask. It could be possible that between block/restore calls procmask for other signals could be changed, and this way - setting procmask directly - we will overwrite them. So be careful, and when restoring SIGSEGV mask, touch mask bit for only that signal. ( we need xsigismember helper to get this done, which is also introduced in this patch )
-
Kirill Smelkov authored
The mistake was there from the beginning - from 3e5e78cd (lib/utils: Small C utilities we'll use).
-
Kirill Smelkov authored
We'll need this for function which return error not in errno - e.g. pthread_sigmask().
-
Kirill Smelkov authored
Non on-pagefault code should not access any not-mmapped memory. Here we just refactor the code we already had to block/restore SIGSEGV from fileh_dirty_writeout() and use it in all functions called from non-pagefaulting context, as promised. This way, if there is an error in virtmem implementation which incorrectly accesses prepared for BigFile maps memory, we'll just die with coredump instead of trying to incorrectly handle the pagefault.
-
- 27 Jul, 2015 1 commit
-
-
Kirill Smelkov authored
ca064f75 (bigarray: Support resizing in-place) added O(1) in-place BigArray.resize() which makes possible for users to append data to BigArray in O(δ) time. But it is easy for people to make off-by-one mistakes when calculating indices for append. So provide a convenient BigArray.append() which simplifies the following A # ZBigArray e.g. of shape (N, 3) values # ndarray to append of shape (δ, 3) n, δ = len(A), len(values) # length of A's major index =N A.resize((n+δ, A.shape[1:])) # add δ new entries ; now len(A) =N+δ A[-δ:] = values # set data for last new δ entries into A.append(values) /cc @klaus
-
- 24 Jul, 2015 1 commit
-
-
Kirill Smelkov authored
We stopped using numpy.multiply in 73926487 (*: It is not safe to use multiply.reduce() - it overflows).
-
- 26 Jun, 2015 4 commits
-
-
Kirill Smelkov authored
-
Kirill Smelkov authored
We compare A_[10*PS-1] (which is A_[1]) to 0, but A_= ndarray ((10*PS,), uint8) and that means the array memory is not initialized. So the comparison works sometimes and sometimes it does not. Initialize compared element explicitly. NOTE: A (without _) element does not need to be initialized - because not-initialized BigArray parts read as zeros.
-
Kirill Smelkov authored
/cc @jm
-
Kirill Smelkov authored
Previously we were always testing with DBs backed up by FileStorage. Now we provide a way to run the testsuite with user selected storage backend: $ WENDELIN_CORE_TEST_DB="<fs>" make test.py # test with temporary db with FileStorage $ WENDELIN_CORE_TEST_DB="<zeo>" make test.py # ----------//---------- with ZEO $ WENDELIN_CORE_TEST_DB="<neo>" make test.py # ----------//---------- with NEO $ WENDELIN_CORE_TEST_DB=neo://db@master make test.py # test with externally provided DB Default is still to run tests with FileStorage. /cc @jm
-
- 25 Jun, 2015 4 commits
-
-
Kirill Smelkov authored
And this way, because dbopen() supports opening various kind of databases (see previous commit) we can now specify type of database on command line, e.g. /path/to/db neo://db@master zeo://host:port /cc @jm
-
Kirill Smelkov authored
Done via manual hacky way for now. The clean solution would be to reuse e.g. repoze.zodbconn[1] or zodburi[2] and teach them to support NEO. But for now we can't -- those eggs depend on ZODB, and we still use ZODB3 for maintaining compatibility with both ZODB3.10 and ZODB4. /cc @jm [1] https://pypi.python.org/pypi/repoze.zodbconn [2] https://pypi.python.org/pypi/zodburi
-
Kirill Smelkov authored
Factor out those routines to open a ZODB database to common place. The reason for doing so is that we'll soon teach dbopen to automatically recognize several protocols, e.g. neo:// and zeo:// and this way, clients who use dbopen() could automatically access storages besides FileStorage.
-
Kirill Smelkov authored
-
- 12 Jun, 2015 1 commit
-
-
Kirill Smelkov authored
-
- 02 Jun, 2015 9 commits
-
-
Kirill Smelkov authored
Because numpy.ndarray does not accept it as buffer= argument https://github.com/numpy/numpy/issues/5935 and our memcpy crashes. NOTE if we'll need to use memoryview, we can adapt our memcpy to use array() directly which works with memoryview, as outlined in the above numpy issue.
-
Kirill Smelkov authored
BigArrays can be big - up to 2^64 bytes, and thus in general it is not possible to represent whole BigArray as ndarray view, because address space is usually smaller on 64bit architectures. However users often try to pass BigArrays to numpy functions as-is, and numpy finds a way to convert, or start converting, BigArray to ndarray - via detecting it as a sequence, and extracting elements one-by-one. Which is slooooow. Because of the above, we provide users a well-defined service: - if virtual address space is available - we succeed at creating ndarray view for whole BigArray, without delay and copying. - if not - we report properly the error and give hint how BigArrays have to be processed in chunks. Verifying that big BigArrays cannot be converted to ndarray also tests for behaviour and issues fixed in last 5 patches. /cc @Tyagov /cc @klaus
-
Kirill Smelkov authored
e.g. In [1]: multiply.reduce((1<<30, 1<<30, 1<<30)) Out[1]: 0 instead of In [2]: (1<<30) * (1<<30) * (1<<30) Out[2]: 1237940039285380274899124224 In [3]: 1<<90 Out[3]: 1237940039285380274899124224 also multiply.reduce returns int64, instead of python int: In [4]: type( multiply.reduce([1,2,3]) ) Out[4]: numpy.int64 which also leads to overflow-related problems if we further compute with this value and other integers and results exceeds int64 - it becomes float: In [5]: idx0_stop = 18446744073709551615 In [6]: stride0 = numpy.int64(1) In [7]: byte0_stop = idx0_stop * stride0 In [8]: byte0_stop Out[8]: 1.8446744073709552e+19 and then it becomes a real problem for BigArray.__getitem__() wendelin.core/bigarray/__init__.py:326: RuntimeWarning: overflow encountered in long_scalars page0_min = min(byte0_start, byte0_stop+byte0_stride) // pagesize # TODO -> fileh.pagesize and then > vma0 = self._fileh.mmap(page0_min, page0_max-page0_min+1) E TypeError: integer argument expected, got float ~~~~ So just avoid multiple.reduce() and do our own mul() properly the same way sum() is builtin into python, and we avoid overflow-related problems.
-
Kirill Smelkov authored
We need this commit: http://git.ozlabs.org/?p=ccan;a=commitdiff;h=c38e11b508e52fb2921e67d1123b05d9bef90fd2 or else we segfault on really big arrays allocation instead of getting ENOMEM and reporting it as MemoryError to python.
-
Kirill Smelkov authored
Consider e.g. this for pyvma: 1. in pyfileh_mmap() pyvma is created 2. next fileh_mmap(pyvma, pyfileh, ...) fails 3. we need to deallocate pyvma which was not mapped 4. in pyvma_dealloc() we unmap pyvma unconditionally -> boom. The same story goes for pyfileh dealloc vs not fully constructing it in pyfileh_open().
-
Kirill Smelkov authored
OverflowError when computing slice indices practically means we'll cannot allocate so much address space at next step: In [1]: s = slice(None) In [2]: s.indices(1<<62) Out[2]: (0, 4611686018427387904, 1) In [3]: s.indices(1<<63) --------------------------------------------------------------------------- OverflowError Traceback (most recent call last) <ipython-input-4-5aa549641bc6> in <module>() ----> 1 s.indices(1<<63) OverflowError: cannot fit 'long' into an index-sized integer So translate this OverflowError into MemoryError (preserving message details), because we'll need such "no so much address space" cases to show up as MemoryError in a sooner patch.
-
Kirill Smelkov authored
Currently we always raise RuntimeError for problems, which is more-or-less ok for humans, but soon we'll need to distinguish "no memory" errors from other error conditions in upper layers in code. Introduce helper function for choosing appropriate exception type for an error - MemoryError for when errno=ENOMEM and RuntimeError otherwise, and use it where appropriate. ( Unfortunately Python does not provide such a helper... )
-
Kirill Smelkov authored
As discussion in https://bitbucket.org/pypa/setuptools/issue/313 unveiled, setuptools do not have to carry setuptools.file_finders entrypoint with it in order to support _other_ projects to use .file_finders entry point. In our case it means that is is normal that we have to make sure that the group we are going to register entry-point into, exists. Remove erroneous comments introduced in 11d130d1 (setup: Ensure setuptools.file_finders entry-point group is registered).
-
Kirill Smelkov authored
py2-only syntax introduced in acf7e91d (setup/runcmd: Properly report errors, if running command is missing): File "setup.py", line 168 except Exception, e: ^ SyntaxError: invalid syntax
-
- 01 Jun, 2015 4 commits
-
-
Kirill Smelkov authored
The reason is some people are blocked to access git:// protocol/port because of firewall setup in their environment, and git.ozlabs.org does not provide a way to clone over http(s). We can provide CCAN mirror and we support cloning it over http(s). Also, it makes sense to not depend on thirdparty sites for hosting strongly-related components.
-
Kirill Smelkov authored
-
Kirill Smelkov authored
We currently use subprocess.check_output() for running external command (git), and check_output() checks running command status code, as its name promises: https://github.com/python/cpython/blob/2.7/Lib/subprocess.py#L569 and reports it appropriately to user: https://github.com/python/cpython/blob/2.7/Lib/subprocess.py#L411 but if the command is not found, it is just single error: [Errno 2] No such file or directory which gets output to the log and it is not clear what command failed, or evern if it is from runcmd() call. Redo our runcmd() manually, so that it always report erros with context - what command it was trying to run. The error now becomes: RuntimeError: (['missing-command'],): [Errno 2] No such file or directory /cc @Tyagov
-
Kirill Smelkov authored
-
- 28 May, 2015 3 commits
-
-
Kirill Smelkov authored
If setuptools.file_finders group is not registered, dist.get_entry_map('setuptools.file_finders') returns just {} not connected to entry map, and further modifications of this dict go nowhere (and thus, our git_lsfiles() is not hooked -> sdist fails to produce correct source archive). This is a workaround for setuptools 9.0 dropping `setuptools.file_finders` entrypoint group registration: https://bitbucket.org/pypa/setuptools/commits/f191c8a1225bd58a5fb5aa9abb31b06dc710f0b9#Lsetup.pyF175 https://bitbucket.org/pypa/setuptools/issue/313 issue reported back: https://bitbucket.org/pypa/setuptools/issue/313#comment-18430008
-
Kirill Smelkov authored
It was hanging with NumPy-1.9 before 425dc5d1 (bigarray: Raise IndexError for out-of-bound element access), because of the following correct NumPy commit: https://github.com/numpy/numpy/commit/d36f8227 and in particular https://github.com/numpy/numpy/commit/d36f8227#diff-6d326badc0872de91e025cbfb0be1aafR522 That PySequence_Fast(obj) (with obj being BigArray) creates iterator on top of obj and before our previous IndexError fix in 425dc5d1, this was looping forever. Test explicitly with both NumPy 1.8 and NumPy 1.9, that this construct does not hang. /cc @Tyagov
-
Kirill Smelkov authored
The way BigArray.__getitem__ works for element access is that for e.g. A[i] it translates the request to A[i:i+1] and remembers to lower the dimensionality at scalar index dim_adjust = (0,) so, in full, A[i] is computed this way: A[i] -> A[i:i+1](0,) ( it is done this way to unify code for scalar / slice access in __getitem__ - see 0c826d5c "BigArray: An ndarray-like on top of BigFile memory mappings" ) The code for slice access also has a shortcut - if it sees that slice results in empty array (e.g. for out-of-bound slice), we can avoid spending time to create a file vma mapping only to create empty view on top of it. In 0c826d5c, that optimization, however forgot to apply the "lower the dimensionality" step on top of resulting empty view, and that turned out for not raising IndexError for out-of-bounds scalar access: A = BigArray((10,), uint8) In [1]: A[0] Out[1]: 0 In [2]: A[1] Out[2]: 0 In [3]: A[2] Out[3]: 0 In [4]: A[9] Out[4]: 0 In [5]: A[10] Out[5]: array([], dtype=uint8) NOTE that A[10] returns empty array instead of raising IndexError. So do not forget to apply the "reduce dimensionality" step for empty views, and this way we get proper IndexError (because for empty view, scalar access results in IndexError). NOTE: this bug was also preventing for e.g. list(A) to work, because list(A) internally works this way: l = [] i = iter(A) for _ in i: l.append(_) but iterating would not stop after 10 elements - after array end, _ will be always array([], dtype=uint8), and thus the loop never finished and memory usage grow to infinity. /cc @Tyagov
-