Module: Familia::Connection

Includes:
Middleware, Operations
Included in:
Familia
Defined in:
lib/familia/connection.rb,
lib/familia/connection/behavior.rb,
lib/familia/connection/handlers.rb,
lib/familia/connection/middleware.rb,
lib/familia/connection/operations.rb,
lib/familia/connection/operation_core.rb,
lib/familia/connection/pipelined_core.rb,
lib/familia/connection/transaction_core.rb,
lib/familia/connection/individual_command_proxy.rb

Overview

The Connection module provides Database connection management for Familia. It allows easy setup and access to Database clients across different URIs with robust connection pooling for thread safety.

Defined Under Namespace

Modules: Behavior, Handler, Middleware, OperationCore, Operations, PipelineCore, TransactionCore Classes: CachedConnectionHandler, CreateConnectionHandler, FiberConnectionHandler, FiberPipelineHandler, FiberTransactionHandler, IndividualCommandProxy, ParentDelegationHandler, ProviderConnectionHandler, ResponsibilityChain, StandaloneConnectionHandler

Constant Summary collapse

DefaultConnectionHandler =
CreateConnectionHandler

Instance Attribute Summary collapse

Attributes included from Middleware

#enable_database_counter, #enable_database_logging

Instance Method Summary collapse

Methods included from Operations

#atomic_write, #pipelined, #transaction, #with_dbclient, #with_isolated_dbclient

Methods included from Middleware

#clear_fiber_connection!, #fiber_connection=, #increment_middleware_version!, #middleware_version, #reconnect!

Instance Attribute Details

#connection_providerProc

The provider should accept a URI string and return a Redis connection already connected to the correct database specified in the URI.

Contract

Familia calls the provider every time it needs a client and never hands the connection back -- there is no check-in hook. The provider must therefore return a client that remains exclusively usable by the caller without a matching check-in.

pool.with { |conn| conn } does NOT satisfy that contract: with checks the connection back in the moment its block returns, so the client handed to Familia is simultaneously available to every other checkout. Under concurrency two threads end up issuing commands on one connection, and per-connection state (MULTI, WATCH, SELECT, SUBSCRIBE) crosses callers.

ConnectionPool::Wrapper (alias ConnectionPool.wrap) does satisfy it: it proxies each command through pool.with, so a connection is checked out for exactly the duration of that command and checked back in afterwards. ConnectionPool's checkout is reentrant per fiber, so a MULTI/EXEC, a pipeline, or a WATCH-guarded transaction still runs entirely on one connection.

Build the pool once, outside the lambda. A ConnectionPool.new inside the provider mints a fresh pool -- and eventually a fresh connection -- on every call.

Examples:

Setting a connection provider

require 'connection_pool'

POOLS = {}
POOLS_MUTEX = Mutex.new

Familia.connection_provider = lambda do |uri|
  POOLS_MUTEX.synchronize do
    POOLS[uri] ||= ConnectionPool::Wrapper.new(size: 10, timeout: 5) do
      Redis.new(url: uri) # uri already carries the logical database
    end
  end
end

Holding one connection per unit of work

# When a request should pin a single connection, check out in the
# provider and check back in at the boundary. Without the check-in a
# bare `checkout` leaks a connection per call and the pool exhausts.
Familia.connection_provider = ->(uri) { POOLS.fetch(uri).checkout }

def call(env)  # Rack middleware / job wrapper
  @app.call(env)
ensure
  POOLS.each_value { |pool| pool.checkin(force: true) }
end

Returns:

  • (Proc)

    A callable that provides Database connections

See Also:

  • Familia::Connection.docs/reference/api-technicaldocs/reference/api-technical.mddocs/reference/api-technical.md#connection-provider-pattern


88
89
90
# File 'lib/familia/connection.rb', line 88

def connection_provider
  @connection_provider
end

#uriURI Also known as: url

Returns The default URI for Database connections.

Returns:

  • (URI)

    The default URI for Database connections



31
32
33
# File 'lib/familia/connection.rb', line 31

def uri
  @uri
end

Instance Method Details

#build_connection_chainObject

Builds the connection chain with handlers in priority order



159
160
161
162
163
164
165
166
# File 'lib/familia/connection.rb', line 159

def build_connection_chain
  ResponsibilityChain.new
    .add_handler(Familia::Connection::FiberPipelineHandler.instance)
    .add_handler(Familia::Connection::FiberTransactionHandler.instance)
    .add_handler(FiberConnectionHandler.new)
    .add_handler(ProviderConnectionHandler.new)
    .add_handler(CreateConnectionHandler.new)
end

#create_dbclient(uri = nil) ⇒ Redis Also known as: connect, isolated_dbclient

Creates a new Database connection instance.

This method always creates a fresh connection and does not use caching. Each call returns a new Redis client instance that you are responsible for managing and closing when done.

Examples:

Creating a new connection

client = Familia.create_dbclient('redis://localhost:6379')
client.ping
client.close

Parameters:

  • uri (String, URI, nil) (defaults to: nil)

    The URI of the Database server to connect to. If nil, uses the default URI from Familia.uri.

Returns:

  • (Redis)

    A new Database client connection.

Raises:

  • (ArgumentError)

    If no URI is specified.



126
127
128
129
130
131
132
133
# File 'lib/familia/connection.rb', line 126

def create_dbclient(uri = nil)
  parsed_uri = normalize_uri(uri)

  # Register middleware only once, globally
  register_middleware_once

  Redis.new(parsed_uri.conf.merge(timeout: 5))
end

#dbclient(uri = nil) ⇒ Redis

Retrieves a Database connection using the Chain of Responsibility pattern. Handles DB selection automatically based on the URI.

Thread-safe: Uses double-checked locking pattern to avoid mutex overhead on the hot path. Only acquires mutex during initial lazy initialization. MRI's GIL provides implicit memory barriers making this pattern safe.

Examples:

Familia.dbclient('redis://localhost:6379/1')

Familia.dbclient(2)  # Use DB 2 with default server

Returns:

  • (Redis)

    The Database client for the specified URI



147
148
149
150
151
152
153
154
155
156
# File 'lib/familia/connection.rb', line 147

def dbclient(uri = nil)
  # Fast path - read with local variable to ensure single read
  chain = @connection_chain
  return chain.handle(uri) if chain

  # Slow path - initialization only
  @connection_chain_mutex.synchronize do
    @connection_chain ||= build_connection_chain
  end.handle(uri)
end

#normalize_uri(uri) ⇒ Object

Normalizes various URI formats to a consistent URI object Made public so handlers can use it



170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
# File 'lib/familia/connection.rb', line 170

def normalize_uri(uri)
  case uri
  when Integer
    new_uri = Familia.uri.dup
    new_uri.db = uri
    new_uri
  when ->(obj) { obj.is_a?(String) || obj.instance_of?(::String) }
    URI.parse(uri)
  when URI
    uri
  when nil
    Familia.uri
  else
    raise ArgumentError, "Invalid URI type: #{uri.class.name}"
  end
end