Class: Neo4j::Driver::Routing::RoutingTable
- Inherits:
-
Object
- Object
- Neo4j::Driver::Routing::RoutingTable
- Defined in:
- lib/neo4j/driver/routing/routing_table.rb
Overview
Snapshot of the cluster's routing roles, plus mutators used by the
error→action handlers (forget on connection failure, forget_writer
on NotALeader / ForbiddenOnReadOnlyDatabase). Mirrors the design of
Java's ClusterRoutingTable + Python's _routing.RoutingTable.
Threading: callers (LoadBalancer) hold @refresh_lock while mutating or reading. Methods here are not internally synchronised.
Constant Summary collapse
- ROLE_TO_KEY =
{ 'READ' => :readers, 'WRITE' => :writers, 'ROUTE' => :routers }.freeze
Instance Attribute Summary collapse
-
#database ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#initialized_without_writers ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#last_updated ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#readers ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#routers ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#ttl ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
-
#writers ⇒ Object
readonly
Servers don't always return a writer right away (e.g. cluster mid-leader-election).
Class Method Summary collapse
-
.from_response(rt, requested_database, clock: Internal::Clock.new) ⇒ Object
Build a table from the
rtmap returned by the BOLT ROUTE message: 1000, db: "homedb", servers: [{addresses: [...], role: "READ", ...]}.
Instance Method Summary collapse
-
#expiration_timestamp ⇒ Object
Absolute expiry in epoch millis (last_updated + ttl).
- #expired?(now = @clock.realtime) ⇒ Boolean
-
#forget(address) ⇒ Object
Remove an address from every role bucket.
-
#forget_writer(address) ⇒ Object
Remove an address only from the writers bucket.
-
#fresh?(readonly:, now: @clock.realtime) ⇒ Boolean
A table is fresh when it isn't expired AND has the servers needed for the requested access mode.
-
#initialize(database:, routers: [], readers: [], writers: [], ttl: 0, clock: Internal::Clock.new) ⇒ RoutingTable
constructor
A new instance of RoutingTable.
-
#purge?(grace:, now: @clock.realtime) ⇒ Boolean
Past the cache grace period? Used by LoadBalancer to drop tables for databases nobody is touching anymore.
-
#servers ⇒ Object
Union of all role buckets — used when shutting down per-server pools that no current routing table references.
- #servers_for(access_mode) ⇒ Object
-
#update(other) ⇒ Object
Replace the contents in place with another table's roles.
Constructor Details
#initialize(database:, routers: [], readers: [], writers: [], ttl: 0, clock: Internal::Clock.new) ⇒ RoutingTable
Returns a new instance of RoutingTable.
43 44 45 46 47 48 49 50 51 52 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 43 def initialize(database:, routers: [], readers: [], writers: [], ttl: 0, clock: Internal::Clock.new) @database = database @routers = routers.to_set @readers = readers.to_set @writers = writers.to_set @ttl = ttl @clock = clock @last_updated = @clock.realtime @initialized_without_writers = @writers.empty? end |
Instance Attribute Details
#database ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def database @database end |
#initialized_without_writers ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def initialized_without_writers @initialized_without_writers end |
#last_updated ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def last_updated @last_updated end |
#readers ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def readers @readers end |
#routers ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def routers @routers end |
#ttl ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def ttl @ttl end |
#writers ⇒ Object (readonly)
Servers don't always return a writer right away (e.g. cluster mid-leader-election). The table is still usable for reads in that window, but the next routing refresh should prefer the initial address (the seed router) over the existing routers list since the latter may have been populated by a leaderless reply.
19 20 21 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 19 def writers @writers end |
Class Method Details
.from_response(rt, requested_database, clock: Internal::Clock.new) ⇒ Object
Build a table from the rt map returned by the BOLT ROUTE message:
{ttl: 1000, db: "homedb", servers: [{addresses: [...], role: "READ"}, ...]}
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 26 def self.from_response(rt, requested_database, clock: Internal::Clock.new) buckets = { readers: [], writers: [], routers: [] } (rt[:servers] || []).each do |server| key = ROLE_TO_KEY[server[:role]] or next (server[:addresses] || []).each { |addr| buckets[key] << ServerAddress.parse(addr) } end new( database: requested_database || rt[:db], routers: buckets[:routers].uniq, readers: buckets[:readers].uniq, writers: buckets[:writers].uniq, ttl: (rt[:ttl] || 0).to_f, clock: clock ) end |
Instance Method Details
#expiration_timestamp ⇒ Object
Absolute expiry in epoch millis (last_updated + ttl). testkit's backend derives the relative ttl from this — uniformly with JRuby, whose Java RoutingTable exposes only this absolute timestamp.
72 73 74 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 72 def ((@last_updated + @ttl).to_f * 1000).round end |
#expired?(now = @clock.realtime) ⇒ Boolean
65 66 67 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 65 def expired?(now = @clock.realtime) now >= @last_updated + @ttl end |
#forget(address) ⇒ Object
Remove an address from every role bucket. Called on connection failure: the server is gone, drop it everywhere.
96 97 98 99 100 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 96 def forget(address) @routers.delete(address) @readers.delete(address) @writers.delete(address) end |
#forget_writer(address) ⇒ Object
Remove an address only from the writers bucket. Called when a WRITE op fails with NotALeader / ForbiddenOnReadOnlyDatabase: the server is alive but no longer the leader, so it can still serve reads.
106 107 108 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 106 def forget_writer(address) @writers.delete(address) end |
#fresh?(readonly:, now: @clock.realtime) ⇒ Boolean
A table is fresh when it isn't expired AND has the servers needed for the requested access mode. An empty writers list is acceptable for read-only acquires (initialized_without_writers state), but a write-mode acquire needs a writer.
58 59 60 61 62 63 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 58 def fresh?(readonly:, now: @clock.realtime) return false if expired?(now) return false if @routers.empty? readonly ? @readers.any? : @writers.any? end |
#purge?(grace:, now: @clock.realtime) ⇒ Boolean
Past the cache grace period? Used by LoadBalancer to drop tables for databases nobody is touching anymore.
78 79 80 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 78 def purge?(grace:, now: @clock.realtime) now >= @last_updated + @ttl + grace end |
#servers ⇒ Object
Union of all role buckets — used when shutting down per-server pools that no current routing table references.
121 122 123 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 121 def servers @routers | @readers | @writers end |
#servers_for(access_mode) ⇒ Object
110 111 112 113 114 115 116 117 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 110 def servers_for(access_mode) case access_mode when :read then @readers when :write then @writers when :route then @routers else raise ArgumentError, "Unknown access mode: #{access_mode.inspect}" end end |
#update(other) ⇒ Object
Replace the contents in place with another table's roles. The existing table identity is preserved so anyone holding a reference sees the new state.
85 86 87 88 89 90 91 92 |
# File 'lib/neo4j/driver/routing/routing_table.rb', line 85 def update(other) @routers = other.routers.dup @readers = other.readers.dup @writers = other.writers.dup @ttl = other.ttl @last_updated = @clock.realtime @initialized_without_writers = @writers.empty? end |