deephacks / lmdbjni

LMDB for Java
Apache License 2.0
204 stars 28 forks source link

broken transactional patterns #23

Closed phraktle closed 9 years ago

phraktle commented 9 years ago

the following pattern is broken:

    Transaction tx = env.createTransaction();
    try {
        return put(tx, key, value, flags);
    } finally {
        tx.commit();
    }

When put throws an error, the commit still runs. Which causes the commit itself to throw an error, masking the original problem.

krisskross commented 9 years ago

When does this happen? Could you provide the error codes for both put and commit?

phraktle commented 9 years ago

The put was throwing an "MDB_PAGE_FULL: Internal error - page has no more space" (the reasons are not yet obvious, but that's besides the point). However, the commit clobbered this with a "MDB_BAD_TXN: Transaction cannot recover - it must be aborted", so you couldn't see the underlying error.

krisskross commented 9 years ago

Is the commit automatically aborted by LMDB when this happens? Or must it be aborted manually to release the transaction?

phraktle commented 9 years ago

The transaction can only be aborted at this point. That's why the commit was throwing MDB_BAD_TXN.

phraktle commented 9 years ago

I'm not sure if there's a resource leak if you don't call abort explicitly, but there's certainly no harm in doing so :)

krisskross commented 9 years ago

Do you mind adding that to commit() in #24? I.e. call abort before throwing the exception?

phraktle commented 9 years ago

With the try-with-resources pattern, that's what actually happens: if the commit failed, the close performs an abort at the end of the block (since self wasn't zeroed by the commit). Doing an abort in commit is confusing.

Plus this pattern takes care of handling any potential suppressed exceptions.

krisskross commented 9 years ago

Yes, but what if you cannot use try-with-resources? Like when using ThreadLocal interceptors for example?

phraktle commented 9 years ago

Then the caller has to ensure closing the resource they have opened. Just like for any other resource, e.g. a file handle.

krisskross commented 9 years ago

I don't want to beat a dead horse but if the transaction knows that it cannot proceed why not abort right away? Just as a measure to prevent resource leaks. The user can still abort manually and this will be a no-op.

This a pretty abnormal situation so taking these extra measures might be appropriate?

phraktle commented 9 years ago

The reason I'm resisting is that I think it's confusing for the commit API call to implicitly also try an abort. And it also makes proper exception handling there nastier (catch potential the exception from commit, then catch potential exception from abort, add that as a suppressed exception to the first Throwable, etc) – while try-with-resources does all this automatically.

In general, most lmdbjni API users will either do atomic get/put calls, or if they do multiple operations in a transaction, they would use try-with-resources (which is much better than the pattern proposed by the current lmdbjni readme).

krisskross commented 9 years ago

Alright, let's settle on this approach.

phraktle commented 9 years ago

Thanks ;)