[Date Prev][Date Next] [Chronological] [Thread] [Top]

Re: (ITS#7774) LMDB assertion failure during Postfix cache cleanup



Hallvard Breien Furuseth:
> [Due to a typo in your e-mail address, the ITS system did not mail out
> this message anywhere.  So I'm CC'ing Howard directly, just in case.]

Sorry about that. Seems everyone screws up sometimes.

> Wietse Venema writes:
> > I wrote a test driver that reliably causes LMDB to abort during a
> > simulated cache cleanup. This "exploit" produces the same result
> > on Linux and FreeBSD, 32-bit and 64-bit systems.
> 
> You're using an old read-only transaction which cannot coexist with:
> - mdb_env_set_mapsize() which moves the map which a cursor in
>   the reader is using.
> - several write-transactions + MDB_NOLOCK. The flag means the writers do
>   not know about the reader, so they reuse pages from the snapshot the
>   reader is using.  The reader can survive while the metapages hold
>   on to its snapshot, i.e. 1 or 2 write commits (I think).
> 
> I don't know if this is a thinko in your program or miscommunication
> between you and Howard about MDB_NOLOCK and mapsize changes.  With the
> current liblmdb, a map change should involve: Remember the reader's
> current position (key), resize the map, renew the txn and cursor, and
> reposition the cursor.

Why do you talk about map size changes when I delete a database entry??

I need a software abstraction layer between Postfix and LMDB that
provides the following generic interface that is independent of
LMDB internals:

1) Give me the first or the next element in the database.

2) Delete, look up, update, a specific database entry.

3) Operations under 1) and 2) must be possible to interleave.

4) Locking must be done outside LMDB, because world-writable lock
   files are not an option.

(There is more, but the above is required to use LMDB as a cache
with periodic cleanup. I am not makig up stuff here - the above
interface has been used in Postfix for many years and it works with
all supported databases that have an iterator.

Sofar the abstraction layer already hides the LMDB-specific MAP_FULL
and MAP_RESIZED error conditions. If this abstraction layer needs
additional code in order to maintain MDB cursor sanity, then please
educate me. 

I had expected that LMDB takes care of its cursors itself, since
the side effects of other API calls are known inside LMDB only.

	Wietse

> The test works if I (a) turn MDB_NOLOCK into MDB_NOTLS (I know that's
> not what you want), and (b) detect map changes in mdb_cursor_get() and
> update the cursor to match.
> Old 'MDB_val's the reader fetched, are invalid after the mapsize change.
> Also, remember that long-lived read-only transactions which write
> transactions do know about, prevent them from reusing pages the reader
> snapshot is using - resulting in further map growth.
> 
> -- 
> Hallvard