Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Context capture for active transactions involves complex and error-prone code #147

Open
njr-11 opened this issue Mar 25, 2019 · 0 comments
Labels
question Further information is requested

Comments

@njr-11
Copy link
Contributor

njr-11 commented Mar 25, 2019

It is error prone, awkward, and requires vendor-specific API in order to place a transaction on the thread for the purpose of capturing context for stages that will run serially within that transaction.

For example,

stage1 = stage.thenApply(fn1);
tx.begin();
try {
    stage2 = stage1.thenCombine(incompleteStage, (v, u) -> {
        try (Connection con = ds.getConnection()) {
             return u + con.createStatement().executeUpdate(sql1);
        } catch (SQLException x) {
             throw new CompletionException(x);
        }
    }).thenApply(u -> {
        try (Connection con = ds.getConnection()) {
             return u + con.createStatement().executeUpdate(sql2);
        } catch (SQLException x) {
             throw new CompletionException(x);
        }
    }).whenComplete((result, failure)) -> {
        try {
            if (failure == null)
                tx.commit();
            else
                tx.rollback();
        } catch (Exception x) {
             throw new CompletionException(x);
        }
    });
} finally {
    // obtain TransactionManager by vendor-specific mechanism, and then do,
    tranManager.suspend();
}
stage2.thenApplyAsync(fn3);
...
// possibly on another thread,
incompleteStage.complete(0);

It is likely out of place for this spec to define aspects of the transaction model. However, an API method such as follows could potentially be more straightforward,

stage.thenApply(fn1)
     .thenCompose(ThreadContext.inTransaction(v -> {
        Connection con = ds.getConnection();
        return incompleteStage.thenApply(u -> {
            try {
                 return u + con.createStatement().executeUpdate(sql1);
            } catch (SQLException x) {
                 throw new CompletionException(x);
            }
        }).thenApply(u -> {
            try {
                return u + con.createStatement().executeUpdate(sql2);
            } catch (SQLException x) {
                 throw new CompletionException(x);
            }
        }); // a whenComplete stage with commit/rollback is auto-added by inTransaction method
     }))
     .thenApplyAsync(fn3);
...
// possibly on another thread,
incompleteStage.complete(0);

There was some preliminary discussion with @mmusgrov on gitter about possible optimizations along these lines, so it seems proper to at least document in an issue. If anything is done here, it is certainly beyond the scope of our initial spec version.

@njr-11 njr-11 added the question Further information is requested label Mar 25, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

1 participant