cohere-transcribe 0.1.1 → 0.1.2

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.
@@ -12,6 +12,47 @@ module Cohere
12
12
  class ModelResources
13
13
  OWNER_GUARD = Monitor.new
14
14
 
15
+ # The global owner record and the ObjectSpace finalizer retain this
16
+ # state, never the ModelResources instance itself. It also lets a new
17
+ # owner synchronously evict a collected predecessor before loading.
18
+ class SessionOwnership
19
+ def initialize
20
+ @mutex = Mutex.new
21
+ @session = nil
22
+ end
23
+
24
+ def session
25
+ @mutex.synchronize { @session }
26
+ end
27
+
28
+ def install(session)
29
+ @mutex.synchronize do
30
+ raise TranscriptionRuntimeError, "ASR session ownership is already installed" if @session
31
+
32
+ @session = session
33
+ end
34
+ end
35
+
36
+ def close
37
+ session = @mutex.synchronize do
38
+ current = @session
39
+ @session = nil
40
+ current
41
+ end
42
+ return unless session
43
+
44
+ session.close
45
+ nil
46
+ end
47
+
48
+ def finalize
49
+ close
50
+ rescue Exception # rubocop:disable Lint/RescueException -- finalizers must not escape during GC or shutdown
51
+ nil
52
+ end
53
+ end
54
+ private_constant :SessionOwnership
55
+
15
56
  class << self
16
57
  def evict_current_asr_owner
17
58
  OWNER_GUARD.synchronize do
@@ -33,16 +74,43 @@ module Cohere
33
74
 
34
75
  reference.__getobj__
35
76
  rescue WeakRef::RefError
36
- @owner_reference = nil
77
+ cleanup_abandoned_owner_locked
37
78
  nil
38
79
  end
39
80
 
40
81
  def claim_locked(owner)
41
82
  @owner_reference = WeakRef.new(owner)
83
+ @owner_ownership = owner.send(:asr_ownership)
42
84
  end
43
85
 
44
86
  def release_locked(owner)
45
- @owner_reference = nil if current_owner_locked.equal?(owner)
87
+ return unless @owner_ownership.equal?(owner.send(:asr_ownership))
88
+
89
+ @owner_reference = nil
90
+ @owner_ownership = nil
91
+ end
92
+
93
+ def cleanup_abandoned_owner_locked
94
+ ownership = @owner_ownership
95
+ @owner_reference = nil
96
+ @owner_ownership = nil
97
+ ownership&.close
98
+ end
99
+
100
+ def finalizer_for(ownership)
101
+ proc { |_object_id| finalize_ownership(ownership) }
102
+ end
103
+
104
+ def finalize_ownership(ownership)
105
+ OWNER_GUARD.synchronize do
106
+ if @owner_ownership.equal?(ownership)
107
+ @owner_reference = nil
108
+ @owner_ownership = nil
109
+ end
110
+ ownership.finalize
111
+ end
112
+ rescue Exception # rubocop:disable Lint/RescueException -- finalizers must not escape during GC or shutdown
113
+ nil
46
114
  end
47
115
  end
48
116
 
@@ -50,9 +118,13 @@ module Cohere
50
118
 
51
119
  def initialize
52
120
  @asr_key = nil
53
- @asr_session = nil
121
+ @asr_ownership = SessionOwnership.new
54
122
  @batch_controller = nil
55
123
  @closed = false
124
+ ObjectSpace.define_finalizer(
125
+ self,
126
+ self.class.send(:finalizer_for, @asr_ownership)
127
+ )
56
128
  end
57
129
 
58
130
  # Returns [session, loaded]. A key change evicts this instance's prior
@@ -62,47 +134,62 @@ module Cohere
62
134
 
63
135
  self.class::OWNER_GUARD.synchronize do
64
136
  ensure_open!
65
- evict_asr_locked if @asr_session && @asr_key != key
137
+ owned_key = immutable_key(key)
138
+ evict_asr_locked if @asr_ownership.session && @asr_key != owned_key
66
139
 
67
140
  owner = self.class.send(:current_owner_locked)
68
141
  owner.send(:evict_asr_locked) if owner && !owner.equal?(self)
69
142
  self.class.send(:claim_locked, self)
70
143
 
71
144
  loaded = false
72
- unless @asr_session
145
+ unless @asr_ownership.session
73
146
  installed = false
147
+ session = nil
74
148
  begin
75
149
  session = yield
76
150
  raise TranscriptionRuntimeError, "ASR loader returned no native session" if session.nil?
77
151
 
78
- @asr_session = session
79
- @asr_key = immutable_key(key)
80
- @batch_controller = nil
81
- loaded = true
82
- installed = true
152
+ Thread.handle_interrupt(Exception => :never) do
153
+ @asr_ownership.install(session)
154
+ @asr_key = owned_key
155
+ @batch_controller = nil
156
+ loaded = true
157
+ installed = true
158
+ end
159
+ rescue Exception # rubocop:disable Lint/RescueException -- roll back asynchronous loader interruption
160
+ begin
161
+ if @asr_ownership.session.equal?(session)
162
+ evict_asr_locked
163
+ else
164
+ session&.close
165
+ end
166
+ rescue Exception # rubocop:disable Lint/RescueException -- preserve the loader failure
167
+ nil
168
+ end
169
+ raise
83
170
  ensure
84
171
  self.class.send(:release_locked, self) unless installed
85
172
  end
86
173
  end
87
- [@asr_session, loaded].freeze
174
+ [@asr_ownership.session, loaded].freeze
88
175
  end
89
176
  end
90
177
 
91
178
  def install_batch_controller(controller)
92
179
  self.class::OWNER_GUARD.synchronize do
93
180
  ensure_open!
94
- raise TranscriptionRuntimeError, "Cannot install an ASR controller before acquiring ASR" unless @asr_session
181
+ raise TranscriptionRuntimeError, "Cannot install an ASR controller before acquiring ASR" unless @asr_ownership.session
95
182
 
96
183
  @batch_controller ||= controller
97
184
  end
98
185
  end
99
186
 
100
187
  def asr_session
101
- self.class::OWNER_GUARD.synchronize { @asr_session }
188
+ self.class::OWNER_GUARD.synchronize { @asr_ownership.session }
102
189
  end
103
190
 
104
191
  def asr?
105
- self.class::OWNER_GUARD.synchronize { !@asr_session.nil? }
192
+ self.class::OWNER_GUARD.synchronize { !@asr_ownership.session.nil? }
106
193
  end
107
194
  alias has_asr? asr?
108
195
 
@@ -123,6 +210,7 @@ module Cohere
123
210
 
124
211
  evict_asr_locked
125
212
  @closed = true
213
+ ObjectSpace.undefine_finalizer(self)
126
214
  end
127
215
  nil
128
216
  end
@@ -133,17 +221,17 @@ module Cohere
133
221
 
134
222
  private
135
223
 
224
+ attr_reader :asr_ownership
225
+
136
226
  def ensure_open!
137
227
  raise TranscriberClosedError, "Model resources have been closed" if @closed
138
228
  end
139
229
 
140
230
  def evict_asr_locked
141
231
  self.class.send(:release_locked, self)
142
- session = @asr_session
143
- @asr_session = nil
144
232
  @asr_key = nil
145
233
  @batch_controller = nil
146
- session&.close
234
+ @asr_ownership.close
147
235
  nil
148
236
  end
149
237
 
@@ -63,7 +63,10 @@ module Cohere
63
63
  # is performed with the POSIX *at family against that descriptor.
64
64
  class BoundDirectory
65
65
  AT_FUNCTION_SIGNATURES = {
66
- openat: [[Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_INT], Fiddle::TYPE_INT],
66
+ openat: [
67
+ [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VARIADIC],
68
+ Fiddle::TYPE_INT
69
+ ],
67
70
  renameat: [
68
71
  [Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP, Fiddle::TYPE_INT, Fiddle::TYPE_VOIDP],
69
72
  Fiddle::TYPE_INT
@@ -294,7 +297,14 @@ module Cohere
294
297
 
295
298
  def open_entry(name, flags, permissions, mode:)
296
299
  validate_entry_name!(name)
297
- file_descriptor = call_at!(:openat, descriptor, name, flags, permissions)
300
+ file_descriptor = call_at!(
301
+ :openat,
302
+ descriptor,
303
+ name,
304
+ flags,
305
+ Fiddle::TYPE_INT,
306
+ permissions
307
+ )
298
308
  handle = File.new(file_descriptor, mode, autoclose: true)
299
309
  file_descriptor = nil
300
310
  handle.close_on_exec = true
@@ -105,18 +105,21 @@ module Cohere
105
105
  return "state marker path for #{format} does not match" unless record.is_a?(Hash) && record["name"] == path.basename.to_s
106
106
 
107
107
  begin
108
- size, sha256 = if directory_binding
109
- bound_output_record(
110
- path,
111
- directory_binding: directory_binding,
112
- guard_bindings: guard_bindings
113
- )
114
- else
115
- stat = path.lstat
116
- return "#{format} output is missing or not regular" unless stat.file? && !stat.symlink?
117
-
118
- [stat.size, Digest::SHA256.file(path).hexdigest]
119
- end
108
+ output_record = if directory_binding
109
+ bound_output_record(
110
+ path,
111
+ directory_binding: directory_binding,
112
+ guard_bindings: guard_bindings
113
+ )
114
+ else
115
+ stat = path.lstat
116
+ return "#{format} output is missing or not regular" unless stat.file? && !stat.symlink?
117
+
118
+ [stat.size, Digest::SHA256.file(path).hexdigest]
119
+ end
120
+ return "#{format} output changed or is not regular" unless output_record
121
+
122
+ size, sha256 = output_record
120
123
  return "#{format} output does not match its state marker" if record["size"] != size || record["sha256"] != sha256
121
124
  rescue Errno::ENOENT
122
125
  return "#{format} output is missing or not regular"
@@ -133,14 +136,25 @@ module Cohere
133
136
  ) do |bound, basename|
134
137
  bound.verify!
135
138
  handle = nil
136
- Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
137
- handle = bound.open_regular(basename)
139
+ begin
140
+ Thread.handle_interrupt(DEFERRED_PUBLICATION_EXCEPTIONS) do
141
+ handle = bound.open_regular(basename)
142
+ end
143
+ rescue TranscriptionRuntimeError
144
+ bound.verify!
145
+ return nil
138
146
  end
139
147
  opened = handle.stat
140
148
  digest = Digest::SHA256.new
141
149
  digest << handle.read(1024 * 1024) until handle.eof?
142
- unless bound.same_regular_entry?(basename, opened)
143
- raise TranscriptionRuntimeError, "Output changed while it was being verified: #{path}"
150
+ unchanged = begin
151
+ bound.same_regular_entry?(basename, opened)
152
+ rescue TranscriptionRuntimeError
153
+ false
154
+ end
155
+ unless unchanged
156
+ bound.verify!
157
+ return nil
144
158
  end
145
159
 
146
160
  bound.verify!
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Cohere
4
4
  module Transcribe
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cohere-transcribe
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ali Hamdi Ali Fadel