Module: Common::Client::Concerns::MhvLockedSessionClient
- Extended by:
- ActiveSupport::Concern
- Includes:
- SentryLogging
- Included in:
- MHVJwtSessionClient, MHVSessionBasedClient
- Defined in:
- lib/common/client/concerns/mhv_locked_session_client.rb
Overview
Module mixin for overriding session logic when making session-based client connections that should lock during session creation, to prevent threads from making simultaneous authentication API calls.
All references to “session” in this module refer to the upstream MHV session.
Constant Summary collapse
- LOCK_RETRY_DELAY =
Number of seconds to wait between attempts to acquire a session lock
0.3
- RETRY_ATTEMPTS =
How many times to attempt await of acquiring a session lock by a preceding request
40
Instance Attribute Summary collapse
-
#session ⇒ Hash
readonly
A hash containing session information.
Instance Method Summary collapse
-
#authenticate ⇒ MhvFhirSessionClient
Ensure the upstream MHV session is not expired or incomplete.
- #initialize(session:) ⇒ Object
- #invalid?(session) ⇒ Boolean protected
-
#lock_and_get_session ⇒ Object
private
Attempt to acquire a redis lock, then create a new MHV session.
- #obtain_redis_lock ⇒ Object private
- #refresh_session(session) ⇒ Object protected
- #release_redis_lock(redis_lock) ⇒ Object private
Methods included from SentryLogging
#log_exception_to_sentry, #log_message_to_sentry, #non_nil_hash?, #normalize_level, #rails_logger, #set_sentry_metadata
Instance Attribute Details
#session ⇒ Hash (readonly)
Returns a hash containing session information.
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 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 18 module MhvLockedSessionClient extend ActiveSupport::Concern include SentryLogging LOCK_RETRY_DELAY = 0.3 # Number of seconds to wait between attempts to acquire a session lock RETRY_ATTEMPTS = 40 # How many times to attempt await of acquiring a session lock by a preceding request attr_reader :session ## # @param session [Hash] a hash containing a key with which the session will be found or built # def initialize(session:) refresh_session(session) end ## # Ensure the upstream MHV session is not expired or incomplete. # # @return [MhvFhirSessionClient] instance of `self` # def authenticate raise 'ICN is required for session creation' unless user_key iteration = 0 # Loop unless a complete, valid MHV session exists, or until max_iterations is reached while invalid?(session) && iteration < RETRY_ATTEMPTS break if lock_and_get_session # Break out of the loop once a new session is created. sleep(LOCK_RETRY_DELAY) # Refresh the MHV session reference in case another thread has updated it. refresh_session(session) iteration += 1 end if invalid?(session) && iteration >= RETRY_ATTEMPTS Rails.logger.info("Failed to create #{@client_session} after #{iteration} attempts to acquire lock") end self end ## # Override client_session method to use extended ::ClientSession classes # class_methods do ## # @return [MedicalRecords::ClientSession] if a MR (Medical Records) client session # @return [Rx::ClientSession] if an Rx (Prescription) client session # @return [SM::ClientSession] if a SM (Secure Messaging) client session # def client_session(klass = nil) @client_session ||= klass end end protected def refresh_session(session) @session = self.class.client_session.find_or_build(session) end def invalid?(session) session.expired? end private ## # Attempt to acquire a redis lock, then create a new MHV session. Once the session is created, # release the lock. # # return [Boolean] true if a session was created, otherwise false # def lock_and_get_session redis_lock = obtain_redis_lock if redis_lock begin @session = get_session return true ensure release_redis_lock(redis_lock) end end false end def obtain_redis_lock lock_key = "mhv_session_lock:#{user_key}" redis_lock = Redis::Namespace.new(REDIS_CONFIG[session_config_key][:namespace], redis: $redis) success = redis_lock.set(lock_key, 1, nx: true, ex: REDIS_CONFIG[session_config_key][:each_ttl]) return redis_lock if success nil end def release_redis_lock(redis_lock) lock_key = "mhv_session_lock:#{user_key}" redis_lock.del(lock_key) end end |
Instance Method Details
#authenticate ⇒ MhvFhirSessionClient
Ensure the upstream MHV session is not expired or incomplete.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 39 def authenticate raise 'ICN is required for session creation' unless user_key iteration = 0 # Loop unless a complete, valid MHV session exists, or until max_iterations is reached while invalid?(session) && iteration < RETRY_ATTEMPTS break if lock_and_get_session # Break out of the loop once a new session is created. sleep(LOCK_RETRY_DELAY) # Refresh the MHV session reference in case another thread has updated it. refresh_session(session) iteration += 1 end if invalid?(session) && iteration >= RETRY_ATTEMPTS Rails.logger.info("Failed to create #{@client_session} after #{iteration} attempts to acquire lock") end self end |
#initialize(session:) ⇒ Object
30 31 32 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 30 def initialize(session:) refresh_session(session) end |
#invalid?(session) ⇒ Boolean (protected)
81 82 83 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 81 def invalid?(session) session.expired? end |
#lock_and_get_session ⇒ Object (private)
Attempt to acquire a redis lock, then create a new MHV session. Once the session is created, release the lock.
return [Boolean] true if a session was created, otherwise false
93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 93 def lock_and_get_session redis_lock = obtain_redis_lock if redis_lock begin @session = get_session return true ensure release_redis_lock(redis_lock) end end false end |
#obtain_redis_lock ⇒ Object (private)
106 107 108 109 110 111 112 113 114 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 106 def obtain_redis_lock lock_key = "mhv_session_lock:#{user_key}" redis_lock = Redis::Namespace.new(REDIS_CONFIG[session_config_key][:namespace], redis: $redis) success = redis_lock.set(lock_key, 1, nx: true, ex: REDIS_CONFIG[session_config_key][:each_ttl]) return redis_lock if success nil end |
#refresh_session(session) ⇒ Object (protected)
77 78 79 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 77 def refresh_session(session) @session = self.class.client_session.find_or_build(session) end |
#release_redis_lock(redis_lock) ⇒ Object (private)
116 117 118 119 |
# File 'lib/common/client/concerns/mhv_locked_session_client.rb', line 116 def release_redis_lock(redis_lock) lock_key = "mhv_session_lock:#{user_key}" redis_lock.del(lock_key) end |