posthog-ruby 3.13.1 → 3.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ecafefeb08db88e91f921ab86cc572b7a0c15a545d778634881ab09a2896a00a
4
- data.tar.gz: c4dd831780adcfc7782fa7d18227bae544760bf50f5a702f709d9e052a1c6411
3
+ metadata.gz: 017e5c02b782e02a2861716768adff003cd203f5996c16e2e8887788f2111fb0
4
+ data.tar.gz: c952a5e44cb1dc847fec2fca8714c886e601b4112151c083508d69f017f6aa4d
5
5
  SHA512:
6
- metadata.gz: cbeb255541f1bca9ba929d659bf7448a9ef6fc884c934e8acccf0cc2788e4afd48a82b9ca9f7bdb3d396fa06225dcfdc15432f07b0c4265dfc01a07f219462e4
7
- data.tar.gz: 83d8be4487d3ac96349723c9c99ce113185a03a37cb6d18466b38b8e512eaef67d2c161340e61c7d9f53ab6075f1f069f8b7928a05818d4e7dd4d4e43f028f43
6
+ metadata.gz: 2b3af5bf281253d498ec03a219282d1b360337f036911e0eacfeb2cfcc0aaa7b0df0b8d69609191769f10e634937e403ca9af047c889e816ecb4837bd3a1e3e5
7
+ data.tar.gz: 2ccc75bf7e0791aa1e38834c2fa3f1900f9073ed889161caab8cb306d403dcfd4bba44ebb7748d752f9838e311c37506a310210ff62e43617e5eae25f7703e7c
@@ -57,6 +57,8 @@ module PostHog
57
57
  # @option opts [String] :host Fully qualified hostname of the PostHog server. Defaults to `https://us.i.posthog.com`.
58
58
  # @option opts [Integer] :max_queue_size Maximum number of calls to remain queued. Defaults to 10_000.
59
59
  # @option opts [Integer] :batch_size Maximum number of events to send in one async batch.
60
+ # @option opts [Numeric] :flush_interval_seconds Maximum seconds to wait for an async batch to fill before sending.
61
+ # Defaults to 5.
60
62
  # @option opts [Boolean] :test_mode +true+ if messages should remain queued for testing. Defaults to +false+.
61
63
  # @option opts [Boolean] :sync_mode +true+ to send events synchronously on the calling thread. Useful in
62
64
  # forking environments like Sidekiq and Resque. Defaults to +false+.
@@ -172,6 +174,7 @@ module PostHog
172
174
 
173
175
  while !@queue.empty? || @worker.is_requesting?
174
176
  ensure_worker_running
177
+ @worker.request_flush
175
178
  sleep(0.1)
176
179
  end
177
180
  end
@@ -972,6 +975,7 @@ module PostHog
972
975
 
973
976
  if queued
974
977
  ensure_worker_running
978
+ @worker.notify
975
979
  true
976
980
  else
977
981
  logger.warn(
@@ -32,6 +32,7 @@ module PostHog
32
32
  module MessageBatch
33
33
  MAX_BYTES = 512_000 # 500Kb
34
34
  MAX_SIZE = 100
35
+ FLUSH_INTERVAL_SECONDS = 5.0 # seconds
35
36
  end
36
37
 
37
38
  module BackoffPolicy
@@ -21,6 +21,16 @@ module PostHog
21
21
  false
22
22
  end
23
23
 
24
+ # @return [void]
25
+ def request_flush
26
+ # Does nothing
27
+ end
28
+
29
+ # @return [void]
30
+ def notify
31
+ # Does nothing
32
+ end
33
+
24
34
  # @return [void]
25
35
  def shutdown
26
36
  # Does nothing
@@ -23,6 +23,7 @@ module PostHog
23
23
  # @param api_key [String] Project API key.
24
24
  # @param options [Hash] Worker options.
25
25
  # @option options [Integer] :batch_size How many items to send in a batch.
26
+ # @option options [Numeric] :flush_interval_seconds Maximum seconds to wait for a batch to fill before sending.
26
27
  # @option options [Proc] :on_error Callback invoked as `on_error.call(status, error)`.
27
28
  # @option options [String] :host PostHog API host URL.
28
29
  # @option options [Boolean] :skip_ssl_verification Disable SSL certificate verification.
@@ -32,44 +33,74 @@ module PostHog
32
33
  @api_key = api_key
33
34
  @on_error = options[:on_error] || proc { |status, error| }
34
35
  batch_size = options[:batch_size] || Defaults::MessageBatch::MAX_SIZE
36
+ flush_interval_seconds = options[:flush_interval_seconds] || Defaults::MessageBatch::FLUSH_INTERVAL_SECONDS
37
+ @flush_interval_seconds = flush_interval_seconds.to_f
35
38
  @batch = MessageBatch.new(batch_size)
36
39
  @lock = Mutex.new
37
- @shutdown_mutex = Mutex.new
40
+ @state_lock = Mutex.new
41
+ @condition = ConditionVariable.new
42
+ @flush_requested = false
38
43
  @shutdown = false
39
- @transport = Transport.new api_host: options[:host], skip_ssl_verification: options[:skip_ssl_verification]
44
+ @pid = Process.pid
45
+ @transport_options = { api_host: options[:host], skip_ssl_verification: options[:skip_ssl_verification] }
46
+ @transport = Transport.new(@transport_options)
40
47
  end
41
48
 
42
49
  # Continuously runs the loop to check for new events.
43
50
  #
44
51
  # @return [void]
45
52
  def run
53
+ ensure_current_process!
54
+
46
55
  until shutdown?
47
- return if @queue.empty?
56
+ wait_for_work
57
+ break if shutdown?
58
+ next if @queue.empty?
48
59
 
49
- @lock.synchronize do
50
- consume_message_from_queue! until @batch.full? || @queue.empty?
51
- end
60
+ build_batch
52
61
 
53
62
  begin
54
- unless @batch.empty?
55
- res = @transport.send @api_key, @batch
56
- handle_error(res.status, res.error) unless res.status == 200
57
- end
63
+ send_batch unless @batch.empty?
58
64
  ensure
59
65
  @lock.synchronize { @batch.clear }
66
+ clear_flush_request_if_idle
60
67
  end
61
68
  end
62
69
  ensure
63
- # Worker threads exit when the queue is drained and are restarted for the
64
- # next burst of events. Close the persistent connection on each exit and
65
- # let Transport reconnect lazily when a future worker sends another batch.
66
- @transport.shutdown
70
+ shutdown_transport
71
+ end
72
+
73
+ # Request the worker to send any pending events without waiting for the
74
+ # configured flush interval. Used by Client#flush and shutdown paths.
75
+ #
76
+ # @return [void]
77
+ def request_flush
78
+ ensure_current_process!
79
+
80
+ @state_lock.synchronize do
81
+ @flush_requested = true
82
+ @condition.broadcast
83
+ end
84
+ end
85
+
86
+ # Wake the worker when producers enqueue new messages.
87
+ #
88
+ # @return [void]
89
+ def notify
90
+ ensure_current_process!
91
+
92
+ @state_lock.synchronize { @condition.signal }
67
93
  end
68
94
 
69
95
  # @return [void]
70
96
  def shutdown
71
- @shutdown_mutex.synchronize { @shutdown = true }
72
- @transport.shutdown
97
+ ensure_current_process!
98
+
99
+ @state_lock.synchronize do
100
+ @shutdown = true
101
+ @flush_requested = true
102
+ @condition.broadcast
103
+ end
73
104
  end
74
105
 
75
106
  # public: Check whether we have outstanding requests.
@@ -77,25 +108,108 @@ module PostHog
77
108
  # @return [Boolean] Whether the worker has outstanding requests.
78
109
  # TODO: Rename to `requesting?` in future version
79
110
  def is_requesting? # rubocop:disable Naming/PredicateName
111
+ ensure_current_process!
112
+
80
113
  @lock.synchronize { !@batch.empty? }
81
114
  end
82
115
 
83
116
  private
84
117
 
85
- def shutdown?
86
- @shutdown_mutex.synchronize { @shutdown }
118
+ def ensure_current_process!
119
+ return if @pid == Process.pid
120
+
121
+ @lock.synchronize { @batch.clear }
122
+ @state_lock.synchronize do
123
+ @pid = Process.pid
124
+ @shutdown = false
125
+ @flush_requested = false
126
+ @condition.broadcast
127
+ end
128
+ shutdown_transport
129
+ @transport = Transport.new(@transport_options)
130
+ end
131
+
132
+ def build_batch
133
+ deadline = monotonic_time + @flush_interval_seconds
134
+
135
+ loop do
136
+ @lock.synchronize do
137
+ consume_message_from_queue! until @batch.full? || @queue.empty?
138
+ end
139
+
140
+ break if @batch.full? || @batch.empty? || flush_requested?
141
+
142
+ remaining = deadline - monotonic_time
143
+ break unless remaining.positive?
144
+
145
+ wait_for_more_messages(remaining)
146
+ end
147
+ end
148
+
149
+ def send_batch
150
+ res = @transport.send @api_key, @batch
151
+ handle_error(res.status, res.error) unless res.status == 200
87
152
  end
88
153
 
89
154
  def consume_message_from_queue!
90
- @batch << @queue.pop
155
+ @batch << @queue.pop(true)
156
+ rescue ThreadError
157
+ # Queue was emptied by another thread between #empty? and #pop.
91
158
  rescue MessageBatch::JSONGenerationError => e
92
159
  handle_error(-1, e.to_s)
93
160
  end
94
161
 
162
+ def wait_for_work
163
+ @state_lock.synchronize do
164
+ while @queue.empty? && !@shutdown
165
+ clear_flush_request_without_lock
166
+ @condition.wait(@state_lock)
167
+ end
168
+ end
169
+ end
170
+
171
+ def wait_for_more_messages(timeout)
172
+ @state_lock.synchronize do
173
+ return if @flush_requested || @shutdown || !@queue.empty?
174
+
175
+ @condition.wait(@state_lock, timeout)
176
+ end
177
+ end
178
+
179
+ def flush_requested?
180
+ @state_lock.synchronize { @flush_requested }
181
+ end
182
+
183
+ def shutdown?
184
+ @state_lock.synchronize { @shutdown }
185
+ end
186
+
187
+ def clear_flush_request
188
+ @state_lock.synchronize { clear_flush_request_without_lock }
189
+ end
190
+
191
+ def clear_flush_request_if_idle
192
+ @state_lock.synchronize { clear_flush_request_without_lock if @queue.empty? }
193
+ end
194
+
195
+ def clear_flush_request_without_lock
196
+ @flush_requested = false
197
+ end
198
+
95
199
  def handle_error(status, error)
96
200
  @on_error.call(status, error)
97
201
  rescue StandardError => e
98
202
  logger.error("Error in on_error callback: #{e.message}")
99
203
  end
204
+
205
+ def shutdown_transport
206
+ @transport.shutdown
207
+ rescue StandardError => e
208
+ logger.error("Error shutting down transport: #{e.message}")
209
+ end
210
+
211
+ def monotonic_time
212
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
213
+ end
100
214
  end
101
215
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module PostHog
4
- VERSION = '3.13.1'
4
+ VERSION = '3.14.0'
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: posthog-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.13.1
4
+ version: 3.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - ''