https://bugs.openldap.org/show_bug.cgi?id=9397
Howard Chu hyc@openldap.org changed:
What |Removed |Added ---------------------------------------------------------------------------- Resolution|--- |WONTFIX Status|UNCONFIRMED |RESOLVED
--- Comment #11 from Howard Chu hyc@openldap.org --- (In reply to github@nicwatson.org from comment #10)
(In reply to Howard Chu from comment #8)
(In reply to tina from comment #7)
Hi,
I am the person who reported the original problem to Nic, after getting my DB corrupted the minute I started doing multiprocessing.
I wanted to comment on this:
(In reply to Howard Chu from comment #3)
1st: "Don't do that." The docs for mdb_env_mapsize() make it clear that it's up to the caller to ensure that no other txns are active at the time it is called. We can first expand this statement in the docs and say that the caller is responsible to ensure that no other *users* are active - processes, threads, txns, whatever.
I think that is far from clear in the documentation, and it seems to be many projects out there (including mine) doing dynamic resizing assuming that no other txns are active *in the current process*! I would urge you to update the documentation then, as this might be a DB corruption waiting to happen in many places.
There's no danger of corruption if you only resize to grow the DB, which is the only functionality you should need in an active application. Shrinking the DB should only be an administrative action, not a live runtime operation.
"only resize to grow the DB" is impossible without external synchronization. Without an external lock, there's always a window between checking the file size and truncating the file when another process might have done the same.
Imagine two processes A and B that both call mdb_env_set_mapsize at the same time on an environment pointing at the same file. Process A calls the function with a size of 20 MiB, process B calls it with a size of 10 MiB. Before the call, the file size was 5 MiB.
I believe the follow sequence is possible.
Process A: 0. mdb_env_set_mapsize begins
Process B:
- mdb_env_set_mapsize begins
Process A:
- ftruncate (inside mdb_env_set_mapsize)(20MiB)
- mmap(20MiB) (inside mdb_env_set_mapsize)
- mdb_env_set_mapsize completes
- mdb_txn_begin
- mdb_cursor_put
Process B: 6. ftruncate(10MiB)
Process A: 7. mdb_txn_commit (bus error due to access past end of file)
If step 7 happens before step 6, then we have potential data loss and a corrupted DB if the process A transaction wrote data between the 10MiB and 20 MiB offsets in the file.
In other words, you can have two separate processes increasing the map size and still truncate real data from the file and/or bus error.
Your scenario is still possible, even if you add a lock in mdb_env_set_mapsize, since the exclusive lock would be released at the end of the set_mapsize call.
IMO it's unrealistic for two unrelated processes to be operating on the same database at the same time. It's unrealistic to expect two unrelated processes to know the schema/layout in the DB. Therefore it only makes sense for this situation to arise if the two processes are separate instances of the same code. And if they're the same code, then they should have the same resizing behavior, i.e. in this example, both of them should want to set the mapsize to the same size.
It's also unrealistic for this scenario to play out exactly as you describe, since the mmap syscall in process A would trigger a context switch. Process A is unlikely to proceed so far before process B can issue its own ftruncate.
Ultimately, it goes against LMDB's basic design to grow the mapsize dynamically in normal usage. The canonical use pattern is to set the mapsize to the amount of available storage space when the DB env is created, and never change it thereafter. Applications that choose to deviate from this use pattern do so at their own risk, it's not proper usage of the library.