Class: FluidFeatures::AppState
- Inherits:
-
Object
- Object
- FluidFeatures::AppState
- Defined in:
- lib/fluidfeatures/app/state.rb
Constant Summary collapse
- USER_ID_NUMERIC =
/^\d+$/
- ETAG_WAIT =
Request to FluidFeatures API to long-poll for max 30 seconds. The API may choose a different duration. If not change in this time, API will return HTTP 304.
ENV["FF_DEV"] ? 5 : 30
- WAIT_BETWEEN_FETCH_SUCCESS =
Hard max of 2 req/sec
0.5
- WAIT_BETWEEN_SEND_SUCCESS_NEXT_WAITING =
Hard max of 10 req/sec
0.1
- WAIT_BETWEEN_FETCH_FAILURES =
If we are failing to communicate with the FluidFeautres API then wait for this long between requests.
5
Instance Attribute Summary collapse
-
#app ⇒ Object
Returns the value of attribute app.
Instance Method Summary collapse
- #configure(app) ⇒ Object
- #feature_version_enabled_for_user(feature_name, version_name, user_id, user_attributes = {}) ⇒ Object
- #features ⇒ Object
- #features=(f) ⇒ Object
- #features_storage ⇒ Object
-
#initialize(app) ⇒ AppState
constructor
seconds.
- #load_state ⇒ Object
- #run_loop ⇒ Object
Constructor Details
#initialize(app) ⇒ AppState
seconds
30 31 32 33 34 |
# File 'lib/fluidfeatures/app/state.rb', line 30 def initialize(app) raise "app invalid : #{app}" unless app.is_a? ::FluidFeatures::App configure(app) run_loop end |
Instance Attribute Details
#app ⇒ Object
Returns the value of attribute app.
11 12 13 |
# File 'lib/fluidfeatures/app/state.rb', line 11 def app @app end |
Instance Method Details
#configure(app) ⇒ Object
36 37 38 39 40 |
# File 'lib/fluidfeatures/app/state.rb', line 36 def configure(app) @app = app @features = features_storage.list @features_lock = ::Mutex.new end |
#feature_version_enabled_for_user(feature_name, version_name, user_id, user_attributes = {}) ⇒ Object
107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 |
# File 'lib/fluidfeatures/app/state.rb', line 107 def feature_version_enabled_for_user(feature_name, version_name, user_id, user_attributes={}) raise "feature_name invalid : #{feature_name}" unless feature_name.is_a? String version_name ||= ::FluidFeatures::DEFAULT_VERSION_NAME raise "version_name invalid : #{version_name}" unless version_name.is_a? String #assert(isinstance(user_id, basestring)) user_attributes ||= {} user_attributes["user"] = user_id.to_s if user_id.is_a? Integer user_id_hash = user_id elsif USER_ID_NUMERIC.match(user_id) user_id_hash = user_id.to_i else user_id_hash = Digest::SHA1.hexdigest(user_id)[-10, 10].to_i(16) end enabled = false feature = features[feature_name] version = feature["versions"][version_name] modulus = user_id_hash % feature["num_parts"] enabled = version["parts"].include? modulus feature["versions"].each_pair do |other_version_name, other_version| if other_version version_attributes = (other_version["enabled"] || {})["attributes"] if version_attributes user_attributes.each_pair do |attr_key, attr_id| version_attribute = version_attributes[attr_key.to_s] if version_attribute and version_attribute.include? attr_id.to_s if other_version_name == version_name # explicitly enabled for this version return true else # explicitly enabled for another version return false end end end end end end enabled end |
#features ⇒ Object
46 47 48 49 50 51 52 |
# File 'lib/fluidfeatures/app/state.rb', line 46 def features f = nil @features_lock.synchronize do f = @features end f end |
#features=(f) ⇒ Object
54 55 56 57 58 59 60 |
# File 'lib/fluidfeatures/app/state.rb', line 54 def features= f return unless f.is_a? Hash @features_lock.synchronize do features_storage.replace(f) unless @features == f @features = f end end |
#features_storage ⇒ Object
42 43 44 |
# File 'lib/fluidfeatures/app/state.rb', line 42 def features_storage @features_storage ||= FluidFeatures::Persistence::Features.create(FluidFeatures.config["cache"]) end |
#load_state ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 |
# File 'lib/fluidfeatures/app/state.rb', line 94 def load_state success, state = app.get("/features", { :verbose => true, :etag_wait => ETAG_WAIT }, true) if success and state state.each_pair do |feature_name, feature| feature["versions"].each_pair do |version_name, version| # convert parts to a Set for quick lookup version["parts"] = Set.new(version["parts"] || []) end end end return success, state end |
#run_loop ⇒ Object
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 |
# File 'lib/fluidfeatures/app/state.rb', line 62 def run_loop Thread.new do while true begin success, state = load_state # Note, success could be true, but state might be nil. # This occurs with 304 (no change) if success and state # switch out current state with new one self.features = state elsif not success # If service is down, then slow our requests # within this thread sleep WAIT_BETWEEN_FETCH_FAILURES end # What ever happens never make more than N requests # per second sleep WAIT_BETWEEN_FETCH_SUCCESS rescue Exception => err # catch errors, so that we do not affect the rest of the application app.logger.error "load_state failed : #{err.}\n#{err.backtrace.join("\n")}" # hold off for a little while and try again sleep WAIT_BETWEEN_FETCH_FAILURES end end end end |