Permalink
Browse files

Have the connection pool remove disconnected connections when the ada…

…pter supports it

This adds a Sequel::DatabaseDisconnectError (subclass of
Sequel::DatabaseError), for signaling to the connection pool that the
connection was lost.  It changes the connection pool code to rescue
that exception and remove the offending connection from the
connection pool.

In order to implement this, a disconnection_proc had to be added to
the connection pool.  Sequel uses a generic one that calls
Database#disconnect_connection.  disconnection_proc is called both
when connections are removed because the connection was lost and
when Database#disconnect is called.  Database#disconnect now calls
@pool.disconnect, which now uses the disconnection_proc if no
block is provided.  All adapters have been modified to remove
Database#disconnect and define Database#disconnect_connection, which
was fairly easy since all defined #disconnect methods just called
@pool.disconnect with a block that disconnected each connection.

The only adapter that currently supports this is PostgreSQL.  The
PostgreSQL adapter used to silently attempt to reconnect, which
might have caused the same SQL to be used twice. I'm not sure it
could have happened, but I'm not sure it couldn't have happened
either.  Now, if the database connection is lost, it raises
DatabaseDisconnectError, the connection pool removes the connection
from the pool, and raises the error to the application.  If the
application wants to continue, it can always retry.

While mucking in the connection pool, I found a bug where the
wrong key could be used when new connections were created.  This
wasn't a huge issue in most cases, but it could have caused as
many as twice the number of max_connections connections to be
created.
  • Loading branch information...
jeremyevans committed Dec 2, 2008
1 parent c075608 commit 2a58c45193ced0c2cf0f6a8da322db0a6b3ae98d
View
@@ -1,5 +1,7 @@
=== HEAD
+* Have the connection pool remove disconnected connections when the adapter supports it (jeremyevans)
+
* Make Dataset#exists return a LiteralString (jeremyevans)
* Support multiple SQL statements in one query in the MySQL adapter (jeremyevans)
@@ -32,10 +32,6 @@ def connect(server)
handle
end
- def disconnect
- @pool.disconnect {|conn| conn.Close}
- end
-
def dataset(opts = nil)
ADO::Dataset.new(self, opts)
end
@@ -49,6 +45,12 @@ def execute(sql, opts={})
end
end
alias_method :do, :execute
+
+ private
+
+ def disconnect_connection(conn)
+ conn.Close
+ end
end
class Dataset < Sequel::Dataset
@@ -20,16 +20,6 @@ def connect(server)
dbc
end
- def disconnect
- @pool.disconnect do |conn|
- rc = SQLDisconnect(conn)
- check_error(rc, "Could not disconnect from database")
-
- rc = SQLFreeHandle(SQL_HANDLE_DBC, conn)
- check_error(rc, "Could not free Database handle")
- end
- end
-
def test_connection(server=nil)
synchronize(server){|conn|}
true
@@ -72,6 +62,14 @@ def check_error(rc, msg)
raise DatabaseError, msg
end
end
+
+ def disconnect_connection(conn)
+ rc = SQLDisconnect(conn)
+ check_error(rc, "Could not disconnect from database")
+
+ rc = SQLFreeHandle(SQL_HANDLE_DBC, conn)
+ check_error(rc, "Could not free Database handle")
+ end
end
class Dataset < Sequel::Dataset
@@ -51,10 +51,6 @@ def connect(server)
::DBI.connect(dbname, opts[:user], opts[:password])
end
- def disconnect
- @pool.disconnect {|c| c.disconnect}
- end
-
def dataset(opts = nil)
DBI::Dataset.new(self, opts)
end
@@ -78,6 +74,12 @@ def do(sql, opts={})
def lowercase
@lowercase ||= false
end
+
+ private
+
+ def disconnect_connection(c)
+ c.disconnect
+ end
end
class Dataset < Sequel::Dataset
@@ -9,10 +9,6 @@ def connect(server)
opts = server_opts(server)
::Informix.connect(opts[:database], opts[:user], opts[:password])
end
-
- def disconnect
- @pool.disconnect{|c| c.close}
- end
def dataset(opts = nil)
Sequel::Informix::Dataset.new(self, opts)
@@ -30,6 +26,12 @@ def execute(sql, opts={})
synchronize(opts[:server]){|c| yield c.cursor(sql)}
end
alias_method :query, :execute
+
+ private
+
+ def disconnect_connection(c)
+ c.close
+ end
end
class Dataset < Sequel::Dataset
@@ -135,11 +135,6 @@ def dataset(opts = nil)
JDBC::Dataset.new(self, opts)
end
- # Close all adapter connections
- def disconnect
- @pool.disconnect {|c| c.close}
- end
-
# Execute the given SQL. If a block is given, if should be a SELECT
# statement or something else that returns rows.
def execute(sql, opts={}, &block)
@@ -223,6 +218,11 @@ def uri(opts={})
private
+ # Close given adapter connections
+ def disconnect_connection(c)
+ c.close
+ end
+
# Execute the prepared statement. If the provided name is a
# dataset, use that as the prepared statement, otherwise use
# it as a key to look it up in the prepared_statements hash.
@@ -132,11 +132,6 @@ def dataset(opts = nil)
MySQL::Dataset.new(self, opts)
end
- # Closes all database connections.
- def disconnect
- @pool.disconnect {|c| c.close}
- end
-
# Executes the given SQL using an available connection, yielding the
# connection if the block is given.
def execute(sql, opts={}, &block)
@@ -216,6 +211,11 @@ def database_name
@opts[:database]
end
+ # Closes given database connection.
+ def disconnect_connection(c)
+ c.close
+ end
+
# Executes a prepared statement on an available connection. If the
# prepared statement already exists for the connection and has the same
# SQL, reuse it, otherwise, prepare the new statement. Because of the
@@ -40,10 +40,6 @@ def connect(server)
conn
end
- def disconnect
- @pool.disconnect {|c| c.disconnect}
- end
-
def dataset(opts = nil)
ODBC::Dataset.new(self, opts)
end
@@ -90,6 +86,11 @@ def transaction(server=nil)
end
end
+ private
+
+ def disconnect_connection(c)
+ c.disconnect
+ end
end
class Dataset < Sequel::Dataset
@@ -15,11 +15,6 @@ def connect(server)
)
end
- def disconnect
- # would this work?
- @pool.disconnect {|c| c.disconnect}
- end
-
def dataset(opts = nil)
OpenBase::Dataset.new(self, opts)
end
@@ -33,6 +28,12 @@ def execute(sql, opts={})
end
end
alias_method :do, :execute
+
+ private
+
+ def disconnect_connection(c)
+ c.disconnect
+ end
end
class Dataset < Sequel::Dataset
@@ -21,10 +21,6 @@ def connect(server)
conn
end
- def disconnect
- @pool.disconnect {|c| c.logoff}
- end
-
def dataset(opts = nil)
Oracle::Dataset.new(self, opts)
end
@@ -57,6 +53,12 @@ def transaction(server=nil)
end
end
end
+
+ private
+
+ def disconnect_connection(c)
+ c.logoff
+ end
end
class Dataset < Sequel::Dataset
@@ -132,12 +132,15 @@ def execute(sql, args=nil)
q = nil
begin
q = args ? async_exec(sql, args) : async_exec(sql)
- rescue PGError => e
- raise if status == Adapter::CONNECTION_OK
- reset
- q = args ? async_exec(sql, args) : async_exec(sql)
+ rescue PGError
+ begin
+ s = status
+ rescue PGError
+ raise(Sequel::DatabaseDisconnectError)
+ end
+ (s == Adapter::CONNECTION_OK) ? raise : raise(Sequel::DatabaseDisconnectError)
ensure
- block
+ block if s == Adapter::CONNECTION_OK
end
begin
block_given? ? yield(q) : q.cmd_tuples
@@ -214,11 +217,6 @@ def dataset(opts = nil)
Postgres::Dataset.new(self, opts)
end
- # Disconnect all active connections.
- def disconnect
- @pool.disconnect {|c| c.finish}
- end
-
# Execute the given SQL with the given args on an available connection.
def execute(sql, opts={}, &block)
return execute_prepared_statement(sql, opts, &block) if Symbol === sql
@@ -254,6 +252,14 @@ def connection_pool_default_options
super.merge(:pool_convert_exceptions=>false)
end
+ # Disconnect given connection
+ def disconnect_connection(conn)
+ begin
+ conn.finish
+ rescue PGError
+ end
+ end
+
# Execute the prepared statement with the given name on an available
# connection, using the given args. If the connection has not prepared
# a statement with the given name yet, prepare it. If the connection
@@ -46,11 +46,6 @@ def dataset(opts = nil)
SQLite::Dataset.new(self, opts)
end
- # Disconnect all connections from the database.
- def disconnect
- @pool.disconnect {|c| c.close}
- end
-
# Run the given SQL with the given arguments and return the number of changed rows.
def execute_dui(sql, opts={})
_execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
@@ -110,6 +105,11 @@ def connection_pool_default_options
o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
o
end
+
+ # Disconnect given connections from the database.
+ def disconnect_connection(c)
+ c.close
+ end
end
# Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
Oops, something went wrong.

0 comments on commit 2a58c45

Please sign in to comment.