Open infinity0 opened 5 years ago
BTW, whether it's legitimate to close the database and then try to perform actions on it, is a separate discussion. My point is that, trying to perform subsequent actions should not result in an indefinite uninterruptible hang, as is the case with the current code. Generally close functions are idempotent or at the very least throw an immediate exception.
Using the LMDB database after closing it will normally result in undefined behavior, likely a crash. An indefinite hang is (arguably) much better.
runInBoundThread
forks off another thread. However, suppose you're writing an application that manages a bunch of threads. Due to POSIX standards [1] one has to code the main function to catch all exceptions, track child threads, cancel the child threads when you get an exception (which may trigger cleanups in each thread) and then wait for these child threads to exit.runInBoundThread
interferes with this process, since it (a) forks off a worker-bound-thread that nobody else knows about, (b) is uninterruptible while this worker-bound-thread runs, and (c) and if it receives an asynchronous exception in the meantime, it will not rethrow this to the worker-bound-thread. (Arguably the combination of (b) and (c) are design bugs and I'll file a GHC bug shortly.)In the case of simple-lmdb this is problematic because the worker-bound-thread is often trying to take a lock e.g. in mdb_txn_begin where it calls
_lockEnv
. If the database has already been closed, then the lock is already taken. Then, the thread will hang forever because it is running inside ofrunInBoundThread
which is uninterruptible.The simple fix is to use
withAsyncBound
andwait
fromControl.Concurrent.Async
- definerunInBoundThread' action = withAsyncBound action wait
and use this instead ofrunInBoundThread
everywhere.[1] these standards dictate that when the main-thread exits, this also causes all child threads to exit immediately without giving the Haskell runtime a chance to run cleanups