Class: ResponseBank::DeferredStore

Inherits:
Object
  • Object
show all
Defined in:
lib/response_bank/deferred_store.rb

Defined Under Namespace

Classes: InvalidContextError, StateError

Constant Summary collapse

ENV_KEY =
'response_bank.deferred_store'
LOCK_OWNED_ENV_KEY =
'response_bank.fill_lock_owned'
TERMINAL_STATES =
%i[aborted completing consumed].freeze
PRIVATE_CACHE_DIRECTIVES =
%w[private no-store].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(env, timestamp:) ⇒ DeferredStore

Returns a new instance of DeferredStore.



51
52
53
54
55
56
57
58
# File 'lib/response_bank/deferred_store.rb', line 51

def initialize(env, timestamp:)
  @env = env
  @timestamp = timestamp
  @cache_key = env.fetch('cacheable.key')
  @owns_lock = env.fetch(LOCK_OWNED_ENV_KEY) == true
  @mutex = Mutex.new
  @state = :requested
end

Class Method Details

.arm_from_middleware(env, status:, headers:) ⇒ Object



28
29
30
# File 'lib/response_bank/deferred_store.rb', line 28

def arm_from_middleware(env, status:, headers:)
  from_env(env)&.__send__(:arm, status: status, headers: headers)
end

.create(env, timestamp:) ⇒ Object



17
18
19
20
21
22
# File 'lib/response_bank/deferred_store.rb', line 17

def create(env, timestamp:)
  validate_context!(env)
  raise InvalidContextError, 'a deferred store is already registered for this request' if env.key?(ENV_KEY)

  new(env, timestamp: timestamp).tap { |store| env[ENV_KEY] = store }
end

.from_env(env) ⇒ Object



24
25
26
# File 'lib/response_bank/deferred_store.rb', line 24

def from_env(env)
  env[ENV_KEY]
end

Instance Method Details

#abortObject



91
92
93
94
95
96
97
98
99
100
101
102
103
# File 'lib/response_bank/deferred_store.rb', line 91

def abort
  transitioned, release_lock = @mutex.synchronize do
    if TERMINAL_STATES.include?(@state)
      [false, false]
    else
      @state = :aborted
      [true, @owns_lock]
    end
  end

  release_owned_lock if release_lock
  transitioned
end

#complete(body:, headers: nil) ⇒ Object

body and headers must describe the complete shared cache representation, not a partial response or bytes personalized for the live client.



62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/response_bank/deferred_store.rb', line 62

def complete(body:, headers: nil)
  status, cached_headers, release_lock = prepare_completion(headers)

  if release_lock
    release_owned_lock
    return false
  end
  return false unless status

  write_started = false
  completed = false
  begin
    CacheWriter.store(
      @env,
      status: status,
      headers: cached_headers,
      body: body,
      timestamp: @timestamp,
      before_write: -> { write_started = true },
    )
    completed = true
  ensure
    @mutex.synchronize { @state = :consumed }
    @env['cacheable.locked'] = false if @owns_lock
    release_owned_lock if @owns_lock && !completed && !write_started
  end
  true
end