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

Better handling of concurrent driver close #291

Merged
merged 6 commits into from
Dec 9, 2016

Conversation

lutovich
Copy link
Contributor

@lutovich lutovich commented Dec 8, 2016

No description provided.

It is possible for connection to be closed from another thread while it is
being used to do some work (send/receive data from socket, etc.). This could
happen when whole driver is closed while there still exist threads doing actual
work.

Each `SocketConnection` is guarded by `ConcurrencyGuardingConnection`
that throws if concurrent access is detected. This wrapper guarded `#close()`
call as well so exceptions could be thrown on `Driver#close()`. Close procedure
would then not be completed and some connections would not be closed.

This commit allows concurrent close in `ConcurrencyGuardingConnection`.
`BlockingPooledConnectionQueue` disposes both idle and acquired connections
when it is terminated. It was previously possible for termination process to
stop halfway if disposal of some connection fails.

This commit makes pool termination try to dispose all connections regardless
of errors.
This pool can be used from multiple threads. It is possible for `#close()` to
be called concurrently with `#acquire()`. Previously we did not guard against
this situation and it was possible to acquire connections from the closed pool.
It could also happen that `#close()` does not actually close all connection
queues because iteration over them is not atomic.

This commit makes `SocketConnectionPool` track if it is closed or not. It also
introduces `Connector` abstraction (which is merely a connection factory) so
that `SocketConnectionPool` can be easily tested.
Init message is send every time we establish a new socket connection. This is
the very first thing connection does. It is possible for the init to fail
(for example when credentials are wrong). Previously socket connection was not
closed after such init failure.

This commit changes `SocketConnector` to close connection on any init failure.
Connection pool (`SocketConnectionPool`) is created before drivers and was
never closed if driver creation failed. This was especially possible with
`RoutingDriver` which tries to build routing table in constructor.

This commit adds closing of the connection pool when creation of driver fails.
It also extracts driver creation logic into an internal class `DriverFactory`
to make it testable.
@lutovich lutovich requested a review from zhenlineo December 8, 2016 16:16
@lutovich lutovich requested a review from fickludd December 8, 2016 16:23
@zhenlineo zhenlineo merged commit e4435e7 into neo4j:1.1 Dec 9, 2016
@@ -64,10 +69,10 @@ public boolean offer( PooledConnection pooledConnection )
pooledConnection.dispose();
}
if (isTerminating.get()) {
PooledConnection poll = queue.poll();
if (poll != null)
PooledConnection connection = queue.poll();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very sensible.

{
return insecure();
}
return new DriverFactory().newInstance( uri, authToken, config.routingSettings(), config );

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know, how about a static factory instance or a static newInstance method? Feels strange to create a stateless Factory object every time, but maybe you have a reason...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. Will get a new PR to fix this if possible

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DriverFactory class has protected methods so classes that extend it can "inject" different behaviour. Thus it is not possible to make #newInstance() static. Allocation of DriverFactory might also be eliminated by the JVM if GraphDatabase#driver() is called often.

catch ( Exception e )
{
assertThat( e, instanceOf( ClientException.class ) );
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One can write a quite useful assert helper for checking exceptions, see org.neo4j.test.assertion.Assert.assertException. If you're doing this often.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am generally against to use exception.expect as it is too hard to tell which line the exception is really thrown. When using it, from the line the exception is declared, any line afterwards could be the place where the exception is thrown.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately we do not have dependencies on artifacts from neo4j/neo4j.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was thinking to simply copy paste that one assert method into some test-util class if you find it useful. Just FYI though, do as you like =).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants