patch

ใƒ‘ใƒƒใƒ - Yusuke Endoh, 05/28/2008 11:31 PM

Download (13.2 KB)

 
1
Index: thread_pthread.c
2
===================================================================
3
--- thread_pthread.c	(revision 16676)
4
+++ thread_pthread.c	(working copy)
5
@@ -418,7 +418,14 @@
6
         }
7
     }
8
 
9
-    th->status = THREAD_STOPPED;
10
+    if (tv) {
11
+	th->status = THREAD_STOPPED;
12
+    }
13
+    else {
14
+	th->status = THREAD_STOPPED_FOREVER;
15
+	th->vm->sleeper++;
16
+	rb_check_deadlock(th->vm);
17
+    }
18
 
19
     thread_debug("native_sleep %ld\n", tv ? tv->tv_sec : -1);
20
     GVL_UNLOCK_BEGIN();
21
@@ -455,9 +462,10 @@
22
 	th->unblock_function_arg = 0;
23
 
24
 	pthread_mutex_unlock(&th->interrupt_lock);
25
-	th->status = prev_status;
26
     }
27
     GVL_UNLOCK_END();
28
+    th->status = prev_status;
29
+    if (!tv) th->vm->sleeper--;
30
     RUBY_VM_CHECK_INTS();
31
 
32
     thread_debug("native_sleep done\n");
33
Index: bootstraptest/test_thread.rb
34
===================================================================
35
--- bootstraptest/test_thread.rb	(revision 16676)
36
+++ bootstraptest/test_thread.rb	(working copy)
37
@@ -268,3 +268,66 @@
38
   at_exit { Fiber.new{}.resume }
39
 }
40
 
41
+assert_equal 'ok', %q{
42
+  begin
43
+    Thread.new { sleep }
44
+    sleep
45
+    :ng
46
+  rescue Exception
47
+    :ok
48
+  end
49
+}
50
+
51
+assert_equal 'ok', %q{
52
+  begin
53
+    m1, m2 = Mutex.new, Mutex.new
54
+    Thread.new { m1.lock; sleep 1; m2.lock }
55
+    m2.lock; sleep 1; m1.lock
56
+    sleep
57
+    :ng
58
+  rescue Exception
59
+    :ok
60
+  end
61
+}
62
+
63
+assert_equal 'ok', %q{
64
+  begin
65
+    m = Mutex.new
66
+    Thread.new { m.lock }; m.lock
67
+    :ok
68
+  rescue Exception
69
+    :ng
70
+  end
71
+}
72
+
73
+assert_equal 'ok', %q{
74
+  begin
75
+    m = Mutex.new
76
+    Thread.new { m.lock }.join; m.lock
77
+    :ok
78
+  rescue Exception
79
+    :ng
80
+  end
81
+}
82
+
83
+assert_equal 'ok', %q{
84
+  begin
85
+    m = Mutex.new
86
+    Thread.new { m.lock; sleep 2 }
87
+    sleep 1; m.lock
88
+    :ok
89
+  rescue Exception
90
+    :ng
91
+  end
92
+}
93
+
94
+assert_equal 'ok', %q{
95
+  begin
96
+    m = Mutex.new
97
+    Thread.new { m.lock; sleep 2; m.unlock }
98
+    sleep 1; m.lock
99
+    :ok
100
+  rescue Exception
101
+    :ng
102
+  end
103
+}
104
Index: vm_core.h
105
===================================================================
106
--- vm_core.h	(revision 16676)
107
+++ vm_core.h	(working copy)
108
@@ -300,6 +300,7 @@
109
     int running;
110
     int thread_abort_on_exception;
111
     unsigned long trace_flag;
112
+    volatile int sleeper;
113
 
114
     /* object management */
115
     VALUE mark_object_ary;
116
@@ -354,6 +355,7 @@
117
     THREAD_TO_KILL,
118
     THREAD_RUNNABLE,
119
     THREAD_STOPPED,
120
+    THREAD_STOPPED_FOREVER,
121
     THREAD_KILLED,
122
 };
123
 
124
@@ -421,6 +423,8 @@
125
     rb_unblock_function_t *unblock_function;
126
     void *unblock_function_arg;
127
     rb_thread_lock_t interrupt_lock;
128
+    VALUE locking_mutex;
129
+    st_table *keeping_mutexes;
130
 
131
     struct rb_vm_tag *tag;
132
     struct rb_vm_trap_tag *trap_tag;
133
Index: thread.c
134
===================================================================
135
--- thread.c	(revision 16676)
136
+++ thread.c	(working copy)
137
@@ -62,6 +62,9 @@
138
 struct timeval rb_time_interval(VALUE);
139
 static int rb_thread_dead(rb_thread_t *th);
140
 
141
+static int unlock_i(st_data_t key, st_data_t val, rb_thread_t *th);
142
+static void rb_check_deadlock(rb_vm_t *vm);
143
+
144
 void rb_signal_exec(rb_thread_t *th, int sig);
145
 void rb_disable_interrupt(void);
146
 
147
@@ -92,13 +95,13 @@
148
   rb_thread_set_current(_th_stored); \
149
 } while(0)
150
 
151
-#define BLOCKING_REGION(exec, ubf, ubfarg) do { \
152
+#define BLOCKING_REGION(exec, ubf, ubfarg, stopped) do { \
153
     rb_thread_t *__th = GET_THREAD(); \
154
     int __prev_status = __th->status; \
155
     rb_unblock_function_t *__oldubf; \
156
     void *__oldubfarg; \
157
     set_unblock_function(__th, ubf, ubfarg, &__oldubf, &__oldubfarg); \
158
-    __th->status = THREAD_STOPPED; \
159
+    if (stopped) __th->status = THREAD_STOPPED; \
160
     thread_debug("enter blocking region (%p)\n", __th); \
161
     GVL_UNLOCK_BEGIN(); {\
162
 	    exec; \
163
@@ -107,10 +110,9 @@
164
     thread_debug("leave blocking region (%p)\n", __th); \
165
     remove_signal_thread_list(__th); \
166
     set_unblock_function(__th, __oldubf, __oldubfarg, 0, 0); \
167
-    if (__th->status == THREAD_STOPPED) { \
168
+    if (stopped && __th->status == THREAD_STOPPED) { \
169
 	__th->status = __prev_status; \
170
     } \
171
-    RUBY_VM_CHECK_INTS(); \
172
 } while(0)
173
 
174
 #if THREAD_DEBUG
175
@@ -197,19 +199,11 @@
176
 set_unblock_function(rb_thread_t *th, rb_unblock_function_t *func, void *arg,
177
 		     rb_unblock_function_t **oldfunc, void **oldarg)
178
 {
179
-  check_ints:
180
-    RUBY_VM_CHECK_INTS(); /* check signal or so */
181
     native_mutex_lock(&th->interrupt_lock);
182
-    if (th->interrupt_flag) {
183
-	native_mutex_unlock(&th->interrupt_lock);
184
-	goto check_ints;
185
-    }
186
-    else {
187
-	if (oldfunc) *oldfunc = th->unblock_function;
188
-	if (oldarg) *oldarg = th->unblock_function_arg;
189
-	th->unblock_function = func;
190
-	th->unblock_function_arg = arg;
191
-    }
192
+    if (oldfunc) *oldfunc = th->unblock_function;
193
+    if (oldarg) *oldarg = th->unblock_function_arg;
194
+    th->unblock_function = func;
195
+    th->unblock_function_arg = arg;
196
     native_mutex_unlock(&th->interrupt_lock);
197
 }
198
 
199
@@ -259,6 +253,11 @@
200
     thread_debug("rb_thread_terminate_all (main thread: %p)\n", th);
201
     st_foreach(vm->living_threads, terminate_i, (st_data_t)th);
202
 
203
+    /* unlock all locking mutexes */
204
+    if (th->keeping_mutexes) {
205
+	st_foreach(th->keeping_mutexes, unlock_i, 0);
206
+    }
207
+
208
     while (!rb_thread_alone()) {
209
 	PUSH_TAG();
210
 	if (EXEC_TAG() == 0) {
211
@@ -354,6 +353,17 @@
212
 	}
213
 	TH_POP_TAG();
214
 
215
+	/* locking_mutex must be Qfalse */
216
+	if (th->locking_mutex != Qfalse) {
217
+	    rb_bug("thread_start_func_2: locking_mutex must be NULL (%p:%p)", th, (void*)th->locking_mutex);
218
+	}
219
+
220
+	/* unlock all locking mutexes */
221
+	if (th->keeping_mutexes) {
222
+	    st_foreach(th->keeping_mutexes, unlock_i, 0);
223
+	}
224
+
225
+	/* delete self from living_threads */
226
 	st_delete_wrap(th->vm->living_threads, th->self);
227
 
228
 	/* wake up joinning threads */
229
@@ -363,7 +373,7 @@
230
 	    rb_thread_interrupt(join_th);
231
 	    join_th = join_th->join_list_next;
232
 	}
233
-	st_delete_wrap(th->vm->living_threads, th->self);
234
+	rb_check_deadlock(th->vm);
235
 
236
 	if (!th->root_fiber) {
237
 	    rb_thread_recycle_stack_release(th->stack);
238
@@ -775,7 +785,8 @@
239
 
240
     BLOCKING_REGION({
241
 	val = func(data1);
242
-    }, ubf, data2);
243
+    }, ubf, data2, 1);
244
+    RUBY_VM_CHECK_INTS();
245
 
246
     return val;
247
 }
248
@@ -1135,6 +1146,7 @@
249
     switch (th->status) {
250
       case THREAD_RUNNABLE:
251
       case THREAD_STOPPED:
252
+      case THREAD_STOPPED_FOREVER:
253
       case THREAD_TO_KILL:
254
 	rb_ary_push(ary, th->self);
255
       default:
256
@@ -1329,6 +1341,7 @@
257
       case THREAD_RUNNABLE:
258
 	return "run";
259
       case THREAD_STOPPED:
260
+      case THREAD_STOPPED_FOREVER:
261
 	return "sleep";
262
       case THREAD_TO_KILL:
263
 	return "aborting";
264
@@ -1428,7 +1441,7 @@
265
 
266
     if (rb_thread_dead(th))
267
 	return Qtrue;
268
-    if (th->status == THREAD_STOPPED)
269
+    if (th->status == THREAD_STOPPED || th->status == THREAD_STOPPED_FOREVER)
270
 	return Qtrue;
271
     return Qfalse;
272
 }
273
@@ -1868,14 +1881,16 @@
274
 		    if (except) *except = orig_except;
275
 		    wait = &wait_100ms;
276
 		} while (__th->interrupt_flag == 0 && (timeout == 0 || subst(timeout, &wait_100ms)));
277
-	    }, 0, 0);
278
+	    }, 0, 0, 1);
279
+	    RUBY_VM_CHECK_INTS();
280
 	} while (result == 0 && (timeout == 0 || subst(timeout, &wait_100ms)));
281
     }
282
 #else
283
     BLOCKING_REGION({
284
 	result = select(n, read, write, except, timeout);
285
 	if (result < 0) lerrno = errno;
286
-    }, ubf_select, GET_THREAD());
287
+    }, ubf_select, GET_THREAD(), 1);
288
+    RUBY_VM_CHECK_INTS();
289
 #endif
290
 
291
     errno = lerrno;
292
@@ -2070,6 +2085,7 @@
293
     st_foreach(vm->living_threads, terminate_atfork_i, (st_data_t)th);
294
     st_clear(vm->living_threads);
295
     st_insert(vm->living_threads, thval, (st_data_t) th->thread_id);
296
+    vm->sleeper = 0;
297
 }
298
 
299
 static int
300
@@ -2096,6 +2112,7 @@
301
     st_foreach(vm->living_threads, terminate_atfork_before_exec_i, (st_data_t)th);
302
     st_clear(vm->living_threads);
303
     st_insert(vm->living_threads, thval, (st_data_t) th->thread_id);
304
+    vm->sleeper = 0;
305
 }
306
 
307
 struct thgroup {
308
@@ -2312,7 +2329,7 @@
309
     rb_thread_lock_t lock;
310
     rb_thread_cond_t cond;
311
     rb_thread_t volatile *th;
312
-    volatile int cond_waiting;
313
+    volatile int cond_waiting, cond_notified;
314
 } mutex_t;
315
 
316
 #define GetMutexPtr(obj, tobj) \
317
@@ -2384,6 +2401,15 @@
318
     return mutex->th ? Qtrue : Qfalse;
319
 }
320
 
321
+static void
322
+mutex_locked(rb_thread_t *th, VALUE self)
323
+{
324
+    if (!th->keeping_mutexes) {
325
+	th->keeping_mutexes = st_init_numtable();
326
+    }
327
+    st_insert(th->keeping_mutexes, self, (st_data_t) th->thread_id);
328
+}
329
+
330
 /*
331
  * call-seq:
332
  *    mutex.try_lock  => true or false
333
@@ -2406,6 +2432,8 @@
334
     if (mutex->th == 0) {
335
 	mutex->th = GET_THREAD();
336
 	locked = Qtrue;
337
+
338
+	mutex_locked(GET_THREAD(), self);
339
     }
340
     native_mutex_unlock(&mutex->lock);
341
 
342
@@ -2413,17 +2441,23 @@
343
 }
344
 
345
 static int
346
-lock_func(rb_thread_t *th, mutex_t *mutex)
347
+lock_func(rb_thread_t *th, mutex_t *mutex, int last_thread)
348
 {
349
     int interrupted = Qfalse;
350
 
351
     native_mutex_lock(&mutex->lock);
352
     while (mutex->th || (mutex->th = th, 0)) {
353
+	if (last_thread) {
354
+	    interrupted = 2;
355
+	    break;
356
+	}
357
+
358
 	mutex->cond_waiting++;
359
 	native_cond_wait(&mutex->cond, &mutex->lock);
360
+	mutex->cond_notified--;
361
 
362
-	if (th->interrupt_flag) {
363
-	    interrupted = Qtrue;
364
+	if (RUBY_VM_INTERRUPTED(th)) {
365
+	    interrupted = 1;
366
 	    break;
367
 	}
368
     }
369
@@ -2438,6 +2472,7 @@
370
     native_mutex_lock(&mutex->lock);
371
     if (mutex->cond_waiting > 0) {
372
 	native_cond_broadcast(&mutex->cond);
373
+	mutex->cond_notified += mutex->cond_waiting;
374
 	mutex->cond_waiting = 0;
375
     }
376
     native_mutex_unlock(&mutex->lock);
377
@@ -2460,11 +2495,30 @@
378
 
379
 	while (mutex->th != th) {
380
 	    int interrupted;
381
+	    int prev_status = th->status;
382
+	    int last_thread = 0;
383
 
384
+	    th->locking_mutex = self;
385
+	    th->status = THREAD_STOPPED_FOREVER;
386
+	    th->vm->sleeper++;
387
+	    if (th->vm->living_threads->num_entries == th->vm->sleeper) {
388
+		last_thread = 1;
389
+	    }
390
+
391
 	    BLOCKING_REGION({
392
-		interrupted = lock_func(th, mutex);
393
-	    }, lock_interrupt, mutex);
394
+		interrupted = lock_func(th, mutex, last_thread);
395
+	    }, lock_interrupt, mutex, 0);
396
 
397
+	    th->locking_mutex = Qfalse;
398
+	    if (interrupted == 2) {
399
+		/* assert: mutex->th != th */
400
+		rb_check_deadlock(th->vm);
401
+	    }
402
+	    th->status = prev_status;
403
+	    th->vm->sleeper--;
404
+
405
+	    if (mutex->th == th) mutex_locked(th, self);
406
+
407
 	    if (interrupted) {
408
 		RUBY_VM_CHECK_INTS();
409
 	    }
410
@@ -2473,15 +2527,8 @@
411
     return self;
412
 }
413
 
414
-/*
415
- * call-seq:
416
- *    mutex.unlock    => self
417
- *
418
- * Releases the lock.
419
- * Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
420
- */
421
-VALUE
422
-rb_mutex_unlock(VALUE self)
423
+static char *
424
+mutex_unlock(VALUE self)
425
 {
426
     mutex_t *mutex;
427
     char *err = NULL;
428
@@ -2501,16 +2548,45 @@
429
 	    /* waiting thread */
430
 	    native_cond_signal(&mutex->cond);
431
 	    mutex->cond_waiting--;
432
+	    mutex->cond_notified++;
433
 	}
434
     }
435
 
436
     native_mutex_unlock(&mutex->lock);
437
 
438
+    return err;
439
+}
440
+
441
+/*
442
+ * call-seq:
443
+ *    mutex.unlock    => self
444
+ *
445
+ * Releases the lock.
446
+ * Raises +ThreadError+ if +mutex+ wasn't locked by the current thread.
447
+ */
448
+VALUE
449
+rb_mutex_unlock(VALUE self)
450
+{
451
+    char *err;
452
+
453
+    err = mutex_unlock(self);
454
+
455
+    if (!err) st_delete_wrap(GET_THREAD()->keeping_mutexes, self);
456
     if (err) rb_raise(rb_eThreadError, err);
457
 
458
     return self;
459
 }
460
 
461
+static int
462
+unlock_i(st_data_t key, st_data_t val, rb_thread_t *th)
463
+{
464
+    VALUE mtxval = key;
465
+
466
+    mutex_unlock(mtxval);
467
+
468
+    return ST_CONTINUE;
469
+}
470
+
471
 static VALUE
472
 rb_mutex_sleep_forever(VALUE time)
473
 {
474
@@ -2579,6 +2655,51 @@
475
     return rb_ensure(func, arg, rb_mutex_unlock, mutex);
476
 }
477
 
478
+static int
479
+check_deadlock_i(st_data_t key, st_data_t val, int *found)
480
+{
481
+    VALUE thval = key;
482
+    rb_thread_t *th;
483
+    GetThreadPtr(thval, th);
484
+
485
+    if (th->status != THREAD_STOPPED_FOREVER) {
486
+	rb_bug("check_deadlock_i: thread that is not THREAD_STOPPED_FOREVER found (%p:%d)", th, th->status);
487
+    }
488
+
489
+    if (RUBY_VM_INTERRUPTED(th)) {
490
+	*found = 1;
491
+    }
492
+    else if (th->locking_mutex) {
493
+	mutex_t *mutex;
494
+	GetMutexPtr(th->locking_mutex, mutex);
495
+
496
+	native_mutex_lock(&mutex->lock);
497
+	if (mutex->th == th || (!mutex->th && mutex->cond_notified)) {
498
+	    *found = 1;
499
+	}
500
+	native_mutex_unlock(&mutex->lock);
501
+    }
502
+
503
+    return (*found) ? ST_STOP : ST_CONTINUE;
504
+}
505
+
506
+static void
507
+rb_check_deadlock(rb_vm_t *vm)
508
+{
509
+    int found = 0;
510
+
511
+    if (vm->living_threads->num_entries != vm->sleeper) return;
512
+
513
+    st_foreach(vm->living_threads, check_deadlock_i, (st_data_t)&found);
514
+
515
+    if (!found) {
516
+	VALUE argv[2];
517
+	argv[0] = rb_eFatal;
518
+	argv[1] = rb_str_new2("deadlock detected");
519
+	rb_thread_raise(2, argv, vm->main_thread);
520
+    }
521
+}
522
+
523
 /*
524
  * Document-class: Barrier
525
  */
526
Index: vm.c
527
===================================================================
528
--- vm.c	(revision 16676)
529
+++ vm.c	(working copy)
530
@@ -1445,6 +1445,13 @@
531
 	    RUBY_FREE_UNLESS_NULL(th->stack);
532
 	}
533
 
534
+	if (th->locking_mutex != Qfalse) {
535
+	    rb_bug("thread_free: locking_mutex must be NULL (%p:%d)", th, th->locking_mutex);
536
+	}
537
+	if (th->keeping_mutexes) {
538
+	    st_free_table(th->keeping_mutexes);
539
+	}
540
+
541
 	if (th->local_storage) {
542
 	    st_free_table(th->local_storage);
543
 	}
544
@@ -1512,6 +1519,12 @@
545
 	RUBY_MARK_UNLESS_NULL(th->root_fiber);
546
 	RUBY_MARK_UNLESS_NULL(th->stat_insn_usage);
547
 
548
+	RUBY_MARK_UNLESS_NULL(th->locking_mutex);
549
+
550
+	if (th->keeping_mutexes) {
551
+	    st_foreach(th->keeping_mutexes, vm_mark_each_thread_func, 0);
552
+	}
553
+
554
 	rb_mark_tbl(th->local_storage);
555
 
556
 	if (GET_THREAD() != th && th->machine_stack_start && th->machine_stack_end) {