File: cache.rb

package info (click to toggle)
ruby-sshkit 1.21.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 700 kB
  • sloc: ruby: 3,522; makefile: 2
file content (83 lines) | stat: -rw-r--r-- 2,231 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
# A Cache holds connections for a given key. Each connection is stored along
# with an expiration time so that its idle duration can be measured.
class SSHKit::Backend::ConnectionPool::Cache
  attr_accessor :key

  def initialize(key, idle_timeout, closer)
    @key = key
    @connections = []
    @connections.extend(MonitorMixin)
    @idle_timeout = idle_timeout
    @closer = closer
  end

  # Remove and return a fresh connection from this Cache. Returns `nil` if
  # the Cache is empty or if all existing connections have gone stale.
  def pop
    connections.synchronize do
      evict
      _, connection = connections.pop
      connection
    end
  end

  # Return a connection to this Cache.
  def push(conn)
    # No need to cache if the connection has already been closed.
    return if closed?(conn)

    connections.synchronize do
      connections.push([Time.now + idle_timeout, conn])
    end
  end

  # Close and remove any connections in this Cache that have been idle for
  # too long.
  def evict
    # Peek at the first connection to see if it is still fresh. If so, we can
    # return right away without needing to use `synchronize`.
    first_expires_at, first_conn = connections.first
    return if (first_expires_at.nil? || fresh?(first_expires_at)) && !closed?(first_conn)

    connections.synchronize do
      fresh, stale = connections.partition do |expires_at, conn|
        fresh?(expires_at) && !closed?(conn)
      end
      connections.replace(fresh)
      stale.each { |_, conn| closer.call(conn) }
    end
  end

  # Close all connections and completely clear the cache.
  def clear
    connections.synchronize do
      connections.map(&:last).each(&closer)
      connections.clear
    end
  end

  def same_key?(other_key)
    key == other_key
  end

  protected

  attr_reader :connections, :idle_timeout, :closer

  private

  def fresh?(expires_at)
    expires_at > Time.now
  end

  def closed?(conn)
    return true if conn.respond_to?(:closed?) && conn.closed?
    # test if connection is alive
    conn.process(0) if conn.respond_to?(:process)
    return false
  rescue IOError => e
    # connection is closed by server
    return true if e.message == 'closed stream'
    raise
  end
end