forked from rubinius/rubinius
-
Notifications
You must be signed in to change notification settings - Fork 0
/
thread.rb
359 lines (291 loc) · 7.71 KB
/
thread.rb
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
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
# -*- encoding: us-ascii -*-
class Thread
def self.current
Rubinius.primitive :thread_current
Kernel.raise PrimitiveFailure, "Thread.current primitive failed"
end
def self.allocate
raise TypeError, "allocator undefined for Thread"
end
def self.pass
Rubinius.primitive :thread_pass
Kernel.raise PrimitiveFailure, "Thread.pass primitive failed"
end
def self.list
Rubinius.primitive :thread_list
Kernel.raise PrimitiveFailure, "Thread.list primitive failed"
end
def fork
Rubinius.primitive :thread_fork
Kernel.raise PrimitiveFailure, "Thread#fork primitive failed"
end
def raise_prim(exc)
Rubinius.primitive :thread_raise
Kernel.raise PrimitiveFailure, "Thread#raise primitive failed"
end
def wakeup
Rubinius.primitive :thread_wakeup
Kernel.raise ThreadError, "Thread#wakeup primitive failed, thread may be dead"
end
def priority
Rubinius.primitive :thread_priority
Kernel.raise ThreadError, "Unable to get Thread priority"
end
def priority=(val)
Rubinius.primitive :thread_set_priority
Kernel.raise ThreadError, "Unable to set Thread priority"
end
def __context__
Rubinius.primitive :thread_context
Kernel.raise PrimitiveFailure, "Thread#__context__ failed"
end
def native_join
Rubinius.primitive :thread_join
Kernel.raise PrimitiveFailure, "Thread#native_join failed"
end
def mri_backtrace
Rubinius.primitive :thread_mri_backtrace
Kernel.raise PrimitiveFailure, "Thread#mri_backtrace primitive failed"
end
def unlock_locks
Rubinius.primitive :thread_unlock_locks
Kernel.raise PrimitiveFailure, "Thread#unlock_locks failed"
end
class Die < Exception; end # HACK
@abort_on_exception = false
def self.abort_on_exception
@abort_on_exception
end
def self.abort_on_exception=(val)
@abort_on_exception = val
end
# It's also an instance method...
def abort_on_exception=(val)
@abort_on_exception = val
end
def abort_on_exception
@abort_on_exception ||= false
end
def inspect
stat = status()
stat = "dead" unless stat
"#<#{self.class}:0x#{object_id.to_s(16)} id=#{@thread_id} #{stat}>"
end
alias_method :to_s, :inspect
def self.new(*args)
thr = Rubinius.invoke_primitive :thread_allocate, self
Rubinius.asm(args, thr) do |args, obj|
run obj
dup
push_false
send :setup, 1, true
pop
run args
push_block
send_with_splat :initialize, 0, true
# no pop here, as .asm blocks imply a pop as they're not
# allowed to leak a stack value
end
unless thr.thread_is_setup?
raise ThreadError, "Thread#initialize not called"
end
return thr
end
def initialize(*args, &block)
unless block
Kernel.raise ThreadError, "no block passed to Thread#initialize"
end
@args = args
@block = block
th_group = Thread.current.group
th_group.add self
begin
fork
rescue Exception => e
th_group.remove self
raise e
end
end
alias_method :__thread_initialize__, :initialize
def thread_is_setup?
@block != nil
end
def alive?
@lock.receive
@alive
ensure
@lock.send nil
end
def stop?
!alive? || @sleep
end
def kill
@dying = true
@sleep = false
self.raise Die
end
alias_method :exit, :kill
alias_method :terminate, :kill
def sleeping?
@lock.receive
@sleep
ensure
@lock.send nil
end
def status
if @alive
if @sleep
"sleep"
elsif @dying
"aborting"
else
"run"
end
elsif @exception
nil
else
false
end
end
def join(timeout = undefined)
join_inner(timeout) { @alive ? nil : self }
end
def group
@group
end
def add_to_group(group)
@group = group
end
def join_inner(timeout = undefined)
result = nil
@lock.receive
begin
if @alive
jc = Rubinius::Channel.new
@joins << jc
@lock.send nil
begin
if timeout.equal? undefined
while true
res = jc.receive
# receive returns false if it was a spurious wakeup
break if res != false
end
else
duration = timeout.to_f
while true
start = Time.now.to_f
res = jc.receive_timeout duration
# receive returns false if it was a spurious wakeup
break if res != false
elapsed = Time.now.to_f - start
duration -= elapsed
break if duration < 0
end
end
ensure
@lock.receive
end
end
Kernel.raise @exception if @exception
result = yield
ensure
@lock.send nil
end
result
end
private :join_inner
def raise(exc=$!, msg=nil, trace=nil)
@lock.receive
unless @alive
@lock.send nil
return self
end
begin
if exc.respond_to? :exception
exc = exc.exception msg
Kernel.raise TypeError, 'exception class/object expected' unless Exception === exc
exc.set_backtrace trace if trace
elsif exc.kind_of? String or !exc
exc = RuntimeError.exception exc
else
Kernel.raise TypeError, 'exception class/object expected'
end
if $DEBUG
STDERR.puts "Exception: #{exc.message} (#{exc.class})"
end
Kernel.raise exc if self == Thread.current
ensure
@lock.send nil
end
raise_prim exc
end
private :raise_prim
def [](key)
locals_aref(Rubinius::Type.coerce_to_symbol(key))
end
def locals_aref(key)
Rubinius.primitive :thread_locals_aref
raise PrimitiveFailure, "Thread#locals_aref primitive failed"
end
private :locals_aref
def []=(key, value)
locals_store(Rubinius::Type.coerce_to_symbol(key), value)
end
def locals_store(key, value)
Rubinius.primitive :thread_locals_store
raise PrimitiveFailure, "Thread#locals_store primitive failed"
end
private :locals_store
def keys
Rubinius.primitive :thread_locals_keys
raise PrimitiveFailure, "Thread#keys primitive failed"
end
def key?(key)
locals_key?(Rubinius::Type.coerce_to_symbol(key))
end
def locals_key?(key)
Rubinius.primitive :thread_locals_has_key
raise PrimitiveFailure, "Thread#locals_key? primitive failed"
end
private :locals_key?
# Register another Thread object +thr+ as the Thread where the debugger
# is running. When the current thread hits a breakpoint, it uses this
# field to figure out who to send variable/scope information to.
#
def set_debugger_thread(thr)
raise TypeError, "Must be another Thread" unless thr.kind_of?(Thread)
@debugger_thread = thr
end
# Called by a debugger thread (a thread where the debugger lives) to
# setup the @control_channel to a Channel object. A debuggee thread
# will send data to this Channel when it hits a breakpoint, allowing
# an easy place for the debugger to wait.
#
# This channel is sent a Tuple containing:
# * The object registered in the breakpoint (can be any object)
# * The Thread object where the breakpoint was hit
# * A Channel object to use to wake up the debuggee thread
# * An Array of Rubinius::Location objects for the stack. These Location
# objects contain references to Rubinius::VariableScope objects
# which contain info like local variables.
#
def setup_control!(chan=nil)
chan ||= Rubinius::Channel.new
@control_channel = chan
return chan
end
def self.main
@main_thread
end
def self.initialize_main_thread(thread)
@main_thread = thread
end
def self.exit
Thread.current.kill
end
def self.kill(thread)
thread.kill
end
alias_method :run, :wakeup
end