File: toggle_fetcher.rb

package info (click to toggle)
ruby-unleash 3.2.5-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 252 kB
  • sloc: ruby: 1,098; makefile: 10; sh: 4
file content (129 lines) | stat: -rwxr-xr-x 4,090 bytes parent folder | download
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
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
121
122
123
124
125
126
127
128
129
require 'unleash/configuration'
require 'net/http'
require 'json'

module Unleash
  class ToggleFetcher
    attr_accessor :toggle_cache, :toggle_lock, :toggle_resource, :etag, :retry_count

    def initialize
      self.etag = nil
      self.toggle_cache = nil
      self.toggle_lock = Mutex.new
      self.toggle_resource = ConditionVariable.new
      self.retry_count = 0

      # start by fetching synchronously, and failing back to reading the backup file.
      begin
        fetch
      rescue StandardError => e
        Unleash.logger.warn "ToggleFetcher was unable to fetch from the network, attempting to read from backup file."
        Unleash.logger.debug "Exception Caught: #{e}"
        read!
      end

      # once initialized, somewhere else you will want to start a loop with fetch()
    end

    def toggles
      self.toggle_lock.synchronize do
        # wait for resource, only if it is null
        self.toggle_resource.wait(self.toggle_lock) if self.toggle_cache.nil?
        return self.toggle_cache
      end
    end

    # rename to refresh_from_server!  ??
    def fetch
      Unleash.logger.debug "fetch()"
      response = Unleash::Util::Http.get(Unleash.configuration.fetch_toggles_url, etag)

      if response.code == '304'
        Unleash.logger.debug "No changes according to the unleash server, nothing to do."
        return
      elsif response.code != '200'
        raise IOError, "Unleash server returned a non 200/304 HTTP result."
      end

      self.etag = response['ETag']
      response_hash = JSON.parse(response.body)

      if response_hash['version'] >= 1
        features = response_hash['features']
      else
        raise NotImplemented, "Version of features provided by unleash server" \
          " is unsupported by this client."
      end

      # always synchronize with the local cache when fetching:
      synchronize_with_local_cache!(features)

      update_running_client!
      save!
    end

    def save!
      Unleash.logger.debug "Will save toggles to disk now"
      begin
        backup_file = Unleash.configuration.backup_file
        backup_file_tmp = "#{backup_file}.tmp"

        self.toggle_lock.synchronize do
          file = File.open(backup_file_tmp, "w")
          file.write(self.toggle_cache.to_json)
          file.close
          File.rename(backup_file_tmp, backup_file)
        end
      rescue StandardError => e
        # This is not really the end of the world. Swallowing the exception.
        Unleash.logger.error "Unable to save backup file. Exception thrown #{e.class}:'#{e}'"
        Unleash.logger.error "stacktrace: #{e.backtrace}"
      ensure
        file&.close if defined?(file)
        self.toggle_lock.unlock if self.toggle_lock.locked?
      end
    end

    private

    def synchronize_with_local_cache!(features)
      if self.toggle_cache != features
        self.toggle_lock.synchronize do
          self.toggle_cache = features
        end

        # notify all threads waiting for this resource to no longer wait
        self.toggle_resource.broadcast
      end
    end

    def update_running_client!
      if Unleash.toggles != self.toggles
        Unleash.logger.info "Updating toggles to main client, there has been a change in the server."
        Unleash.toggles = self.toggles
      end
    end

    def read!
      Unleash.logger.debug "read!()"
      return nil unless File.exist?(Unleash.configuration.backup_file)

      begin
        file = File.new(Unleash.configuration.backup_file, "r")
        file_content = file.read

        backup_as_hash = JSON.parse(file_content)
        synchronize_with_local_cache!(backup_as_hash)
        update_running_client!
      rescue IOError => e
        Unleash.logger.error "Unable to read the backup_file: #{e}"
      rescue JSON::ParserError => e
        Unleash.logger.error "Unable to parse JSON from existing backup_file: #{e}"
      rescue StandardError => e
        Unleash.logger.error "Unable to extract valid data from backup_file. Exception thrown: #{e}"
      ensure
        file&.close
      end
    end
  end
end