summaryrefslogtreecommitdiff
path: root/ext/thread/thread.c
diff options
context:
space:
mode:
authorglass <glass@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-09-06 15:15:07 +0000
committerglass <glass@b2dd03c8-39d4-4d8f-98ff-823fe69b080e>2013-09-06 15:15:07 +0000
commite334bb2ce5d8876b020ab681f21595e2e1c9d601 (patch)
tree71caf522afa33520e80288917bf7257e66ae20a9 /ext/thread/thread.c
parentf43d9e22f688751479bc5c9d85f056091145c960 (diff)
* common.mk: use RUNRUBY instead of MINIRUBY because MINIRUBY can't
require extension libraries. The patch is from nobu (Nobuyoshi Nakada). * ext/thread/extconf.rb: for build ext/thread/thread.c. * include/ruby/intern.h: ditto. * thread.c: ditto. * lib/thread.rb: removed and replaced by ext/thread/thread.c. * ext/thread/thread.c: Queue, SizedQueue and ConditionVariable implementations in C. This patch is based on patches from panaggio (Ricardo Panaggio) and funny_falcon (Yura Sokolov) and ko1 (Koichi Sasada). [ruby-core:31513] [Feature #3620] * test/thread/test_queue.rb (test_queue_thread_raise): add a test for ensuring that killed thread should be removed from waiting threads. It is based on a code by ko1 (Koichi Sasada). [ruby-core:45950] git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@42862 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
Diffstat (limited to 'ext/thread/thread.c')
-rw-r--r--ext/thread/thread.c566
1 files changed, 566 insertions, 0 deletions
diff --git a/ext/thread/thread.c b/ext/thread/thread.c
new file mode 100644
index 0000000000..4065bc5b64
--- /dev/null
+++ b/ext/thread/thread.c
@@ -0,0 +1,566 @@
+#include <ruby.h>
+
+enum {
+ CONDVAR_WAITERS = 0
+};
+
+enum {
+ QUEUE_QUE = 0,
+ QUEUE_WAITERS = 1,
+ SZQUEUE_WAITERS = 2,
+ SZQUEUE_MAX = 3
+};
+
+#define GET_CONDVAR_WAITERS(cv) RSTRUCT_GET((cv), CONDVAR_WAITERS)
+
+#define GET_QUEUE_QUE(q) RSTRUCT_GET((q), QUEUE_QUE)
+#define GET_QUEUE_WAITERS(q) RSTRUCT_GET((q), QUEUE_WAITERS)
+#define GET_SZQUEUE_WAITERS(q) RSTRUCT_GET((q), SZQUEUE_WAITERS)
+#define GET_SZQUEUE_MAX(q) RSTRUCT_GET((q), SZQUEUE_MAX)
+#define GET_SZQUEUE_ULONGMAX(q) NUM2ULONG(GET_SZQUEUE_MAX(q))
+
+static VALUE
+ary_buf_new(void)
+{
+ return rb_ary_tmp_new(1);
+}
+
+static void
+wakeup_first_thread(VALUE list)
+{
+ VALUE thread;
+
+ while (!NIL_P(thread = rb_ary_shift(list))) {
+ if (RTEST(rb_thread_wakeup_alive(thread))) break;
+ }
+}
+
+static void
+wakeup_all_threads(VALUE list)
+{
+ VALUE thread;
+ long i;
+
+ for (i=0; i<RARRAY_LEN(list); i++) {
+ thread = RARRAY_AREF(list, i);
+ rb_thread_wakeup_alive(thread);
+ }
+ rb_ary_clear(list);
+}
+
+/*
+ * Document-class: ConditionVariable
+ *
+ * ConditionVariable objects augment class Mutex. Using condition variables,
+ * it is possible to suspend while in the middle of a critical section until a
+ * resource becomes available.
+ *
+ * Example:
+ *
+ * require 'thread'
+ *
+ * mutex = Mutex.new
+ * resource = ConditionVariable.new
+ *
+ * a = Thread.new {
+ * mutex.synchronize {
+ * # Thread 'a' now needs the resource
+ * resource.wait(mutex)
+ * # 'a' can now have the resource
+ * }
+ * }
+ *
+ * b = Thread.new {
+ * mutex.synchronize {
+ * # Thread 'b' has finished using the resource
+ * resource.signal
+ * }
+ * }
+ */
+
+/*
+ * Document-method: new
+ * call-seq: new
+ *
+ * Creates a new condvar.
+ */
+
+static VALUE
+rb_condvar_initialize(VALUE self)
+{
+ RSTRUCT_SET(self, CONDVAR_WAITERS, ary_buf_new());
+ return self;
+}
+
+struct sleep_call {
+ VALUE mutex;
+ VALUE timeout;
+};
+
+static ID id_sleep;
+
+static VALUE
+do_sleep(VALUE args)
+{
+ struct sleep_call *p = (struct sleep_call *)args;
+ return rb_funcall2(p->mutex, id_sleep, 1, &p->timeout);
+}
+
+static VALUE
+delete_current_thread(VALUE ary)
+{
+ return rb_ary_delete(ary, rb_thread_current());
+}
+
+/*
+ * Document-method: wait
+ * call-seq: wait(mutex, timeout=nil)
+ *
+ * Releases the lock held in +mutex+ and waits; reacquires the lock on wakeup.
+ *
+ * If +timeout+ is given, this method returns after +timeout+ seconds passed,
+ * even if no other thread doesn't signal.
+ */
+
+static VALUE
+rb_condvar_wait(int argc, VALUE *argv, VALUE self)
+{
+ VALUE waiters = GET_CONDVAR_WAITERS(self);
+ VALUE mutex, timeout;
+ struct sleep_call args;
+
+ rb_scan_args(argc, argv, "11", &mutex, &timeout);
+
+ args.mutex = mutex;
+ args.timeout = timeout;
+ rb_ary_push(waiters, rb_thread_current());
+ rb_ensure(do_sleep, (VALUE)&args, delete_current_thread, waiters);
+
+ return self;
+}
+
+/*
+ * Document-method: signal
+ * call-seq: signal
+ *
+ * Wakes up the first thread in line waiting for this lock.
+ */
+
+static VALUE
+rb_condvar_signal(VALUE self)
+{
+ wakeup_first_thread(GET_CONDVAR_WAITERS(self));
+ return self;
+}
+
+/*
+ * Document-method: broadcast
+ * call-seq: broadcast
+ *
+ * Wakes up all threads waiting for this lock.
+ */
+
+static VALUE
+rb_condvar_broadcast(VALUE self)
+{
+ wakeup_all_threads(GET_CONDVAR_WAITERS(self));
+ return self;
+}
+
+/*
+ * Document-class: Queue
+ *
+ * This class provides a way to synchronize communication between threads.
+ *
+ * Example:
+ *
+ * require 'thread'
+ * queue = Queue.new
+ *
+ * producer = Thread.new do
+ * 5.times do |i|
+ * sleep rand(i) # simulate expense
+ * queue << i
+ * puts "#{i} produced"
+ * end
+ * end
+ *
+ * consumer = Thread.new do
+ * 5.times do |i|
+ * value = queue.pop
+ * sleep rand(i/2) # simulate expense
+ * puts "consumed #{value}"
+ * end
+ * end
+ *
+ */
+
+/*
+ * Document-method: new
+ * call-seq: new
+ *
+ * Creates a new queue.
+ */
+
+static VALUE
+rb_queue_initialize(VALUE self)
+{
+ RSTRUCT_SET(self, QUEUE_QUE, ary_buf_new());
+ RSTRUCT_SET(self, QUEUE_WAITERS, ary_buf_new());
+ return self;
+}
+
+static VALUE
+queue_do_push(VALUE self, VALUE obj)
+{
+ rb_ary_push(GET_QUEUE_QUE(self), obj);
+ wakeup_first_thread(GET_QUEUE_WAITERS(self));
+ return self;
+}
+
+/*
+ * Document-method: push
+ * call-seq: push(obj)
+ *
+ * Pushes +obj+ to the queue.
+ */
+
+static VALUE
+rb_queue_push(VALUE self, VALUE obj)
+{
+ return queue_do_push(self, obj);
+}
+
+static unsigned long
+queue_length(VALUE self)
+{
+ return RARRAY_LEN(GET_QUEUE_QUE(self));
+}
+
+static unsigned long
+queue_num_waiting(VALUE self)
+{
+ return RARRAY_LEN(GET_QUEUE_WAITERS(self));
+}
+
+struct waiting_delete {
+ VALUE waiting;
+ VALUE th;
+};
+
+static VALUE
+queue_delete_from_waiting(struct waiting_delete *p)
+{
+ rb_ary_delete(p->waiting, p->th);
+ return Qnil;
+}
+
+static VALUE
+queue_do_pop(VALUE self, VALUE should_block)
+{
+ struct waiting_delete args;
+ args.waiting = GET_QUEUE_WAITERS(self);
+ args.th = rb_thread_current();
+
+ while (queue_length(self) == 0) {
+ if (!(int)should_block) {
+ rb_raise(rb_eThreadError, "queue empty");
+ }
+ rb_ary_push(args.waiting, args.th);
+ rb_ensure((VALUE (*)())rb_thread_sleep_deadly, (VALUE)0, queue_delete_from_waiting, (VALUE)&args);
+ }
+
+ return rb_ary_shift(GET_QUEUE_QUE(self));
+}
+
+static VALUE
+queue_pop_should_block(int argc, VALUE *argv)
+{
+ VALUE should_block = Qtrue;
+ switch (argc) {
+ case 0:
+ break;
+ case 1:
+ should_block = RTEST(argv[0]) ? Qtrue : Qfalse;
+ break;
+ default:
+ rb_raise(rb_eArgError, "wrong number of arguments (%d for 1)", argc);
+ }
+ return should_block;
+}
+
+/*
+ * Document-method: pop
+ * call_seq: pop(non_block=false)
+ *
+ * Retrieves data from the queue. If the queue is empty, the calling thread is
+ * suspended until data is pushed onto the queue. If +non_block+ is true, the
+ * thread isn't suspended, and an exception is raised.
+ */
+
+static VALUE
+rb_queue_pop(int argc, VALUE *argv, VALUE self)
+{
+ VALUE should_block = queue_pop_should_block(argc, argv);
+ return queue_do_pop(self, should_block);
+}
+
+/*
+ * Document-method: empty?
+ * call-seq: empty?
+ *
+ * Returns +true+ if the queue is empty.
+ */
+
+static VALUE
+rb_queue_empty_p(VALUE self)
+{
+ return queue_length(self) == 0 ? Qtrue : Qfalse;
+}
+
+/*
+ * Document-method: clear
+ * call-seq: clear
+ *
+ * Removes all objects from the queue.
+ */
+
+static VALUE
+rb_queue_clear(VALUE self)
+{
+ rb_ary_clear(GET_QUEUE_QUE(self));
+ return self;
+}
+
+/*
+ * Document-method: length
+ * call-seq: length
+ *
+ * Returns the length of the queue.
+ */
+
+static VALUE
+rb_queue_length(VALUE self)
+{
+ unsigned long len = queue_length(self);
+ return ULONG2NUM(len);
+}
+
+/*
+ * Document-method: num_waiting
+ * call-seq: num_waiting
+ *
+ * Returns the number of threads waiting on the queue.
+ */
+
+static VALUE
+rb_queue_num_waiting(VALUE self)
+{
+ unsigned long len = queue_num_waiting(self);
+ return ULONG2NUM(len);
+}
+
+/*
+ * Document-class: SizedQueue
+ *
+ * This class represents queues of specified size capacity. The push operation
+ * may be blocked if the capacity is full.
+ *
+ * See Queue for an example of how a SizedQueue works.
+ */
+
+/*
+ * Document-method: new
+ * call-seq: new(max)
+ *
+ * Creates a fixed-length queue with a maximum size of +max+.
+ */
+
+static VALUE
+rb_szqueue_initialize(VALUE self, VALUE vmax)
+{
+ long max;
+
+ max = NUM2LONG(vmax);
+ if (max <= 0) {
+ rb_raise(rb_eArgError, "queue size must be positive");
+ }
+
+ RSTRUCT_SET(self, QUEUE_QUE, ary_buf_new());
+ RSTRUCT_SET(self, QUEUE_WAITERS, ary_buf_new());
+ RSTRUCT_SET(self, SZQUEUE_WAITERS, ary_buf_new());
+ RSTRUCT_SET(self, SZQUEUE_MAX, vmax);
+
+ return self;
+}
+
+/*
+ * Document-method: max
+ * call-seq: max
+ *
+ * Returns the maximum size of the queue.
+ */
+
+static VALUE
+rb_szqueue_max_get(VALUE self)
+{
+ return GET_SZQUEUE_MAX(self);
+}
+
+/*
+ * Document-method: max=
+ * call-seq: max=(n)
+ *
+ * Sets the maximum size of the queue.
+ */
+
+static VALUE
+rb_szqueue_max_set(VALUE self, VALUE vmax)
+{
+ long max = NUM2LONG(vmax), diff = 0;
+ VALUE t;
+
+ if (max <= 0) {
+ rb_raise(rb_eArgError, "queue size must be positive");
+ }
+ if ((unsigned long)max > GET_SZQUEUE_ULONGMAX(self)) {
+ diff = max - GET_SZQUEUE_ULONGMAX(self);
+ }
+ RSTRUCT_SET(self, SZQUEUE_MAX, vmax);
+ while (diff > 0 && !NIL_P(t = rb_ary_shift(GET_QUEUE_QUE(self)))) {
+ rb_thread_wakeup_alive(t);
+ }
+ return vmax;
+}
+
+/*
+ * Document-method: push
+ * call-seq: push(obj)
+ *
+ * Pushes +obj+ to the queue. If there is no space left in the queue, waits
+ * until space becomes available.
+ */
+
+static VALUE
+rb_szqueue_push(VALUE self, VALUE obj)
+{
+ struct waiting_delete args;
+ args.waiting = GET_QUEUE_WAITERS(self);
+ args.th = rb_thread_current();
+
+ while (queue_length(self) >= GET_SZQUEUE_ULONGMAX(self)) {
+ rb_ary_push(args.waiting, args.th);
+ rb_ensure((VALUE (*)())rb_thread_sleep_deadly, (VALUE)0, queue_delete_from_waiting, (VALUE)&args);
+ }
+ return queue_do_push(self, obj);
+}
+
+static VALUE
+szqueue_do_pop(VALUE self, VALUE should_block)
+{
+ VALUE retval = queue_do_pop(self, should_block);
+
+ if (queue_length(self) < GET_SZQUEUE_ULONGMAX(self)) {
+ wakeup_first_thread(GET_SZQUEUE_WAITERS(self));
+ }
+
+ return retval;
+}
+
+/*
+ * Document-method: pop
+ * call_seq: pop(non_block=false)
+ *
+ * Returns the number of threads waiting on the queue.
+ */
+
+static VALUE
+rb_szqueue_pop(int argc, VALUE *argv, VALUE self)
+{
+ VALUE should_block = queue_pop_should_block(argc, argv);
+ return szqueue_do_pop(self, should_block);
+}
+
+/*
+ * Document-method: pop
+ * call_seq: pop(non_block=false)
+ *
+ * Returns the number of threads waiting on the queue.
+ */
+
+static VALUE
+rb_szqueue_num_waiting(VALUE self)
+{
+ long len = queue_num_waiting(self);
+ len += RARRAY_LEN(GET_SZQUEUE_WAITERS(self));
+ return ULONG2NUM(len);
+}
+
+#ifndef UNDER_THREAD
+#define UNDER_THREAD 1
+#endif
+
+void
+Init_thread(void)
+{
+#if UNDER_THREAD
+#define ALIAS_GLOBCAL_CONST(name) do { \
+ ID id = rb_intern_const(#name); \
+ if (!rb_const_defined_at(rb_cObject, id)) { \
+ rb_const_set(rb_cObject, id, rb_c##name); \
+ } \
+ } while (0)
+#else
+#define ALIAS_GLOBCAL_CONST(name) do { /* nothing */ } while (0)
+#endif
+
+ VALUE rb_cConditionVariable = rb_struct_define_without_accessor_under(
+ UNDER_THREAD ? rb_cThread : 0,
+ "ConditionVariable", rb_cObject, rb_struct_alloc_noinit,
+ "waiters", NULL);
+ VALUE rb_cQueue = rb_struct_define_without_accessor_under(
+ UNDER_THREAD ? rb_cThread : 0,
+ "Queue", rb_cObject, rb_struct_alloc_noinit,
+ "que", "waiters", NULL);
+ VALUE rb_cSizedQueue = rb_struct_define_without_accessor_under(
+ UNDER_THREAD ? rb_cThread : 0,
+ "SizedQueue", rb_cQueue, rb_struct_alloc_noinit,
+ "que", "waiters", "queue_waiters", "size", NULL);
+
+ id_sleep = rb_intern("sleep");
+
+ rb_define_method(rb_cConditionVariable, "initialize", rb_condvar_initialize, 0);
+ rb_define_method(rb_cConditionVariable, "wait", rb_condvar_wait, -1);
+ rb_define_method(rb_cConditionVariable, "signal", rb_condvar_signal, 0);
+ rb_define_method(rb_cConditionVariable, "broadcast", rb_condvar_broadcast, 0);
+
+ rb_define_method(rb_cQueue, "initialize", rb_queue_initialize, 0);
+ rb_define_method(rb_cQueue, "push", rb_queue_push, 1);
+ rb_define_method(rb_cQueue, "pop", rb_queue_pop, -1);
+ rb_define_method(rb_cQueue, "empty?", rb_queue_empty_p, 0);
+ rb_define_method(rb_cQueue, "clear", rb_queue_clear, 0);
+ rb_define_method(rb_cQueue, "length", rb_queue_length, 0);
+ rb_define_method(rb_cQueue, "num_waiting", rb_queue_num_waiting, 0);
+
+ rb_alias(rb_cQueue, rb_intern("enq"), rb_intern("push"));
+ rb_alias(rb_cQueue, rb_intern("<<"), rb_intern("push"));
+ rb_alias(rb_cQueue, rb_intern("deq"), rb_intern("pop"));
+ rb_alias(rb_cQueue, rb_intern("shift"), rb_intern("pop"));
+ rb_alias(rb_cQueue, rb_intern("size"), rb_intern("length"));
+
+ rb_define_method(rb_cSizedQueue, "initialize", rb_szqueue_initialize, 1);
+ rb_define_method(rb_cSizedQueue, "max", rb_szqueue_max_get, 0);
+ rb_define_method(rb_cSizedQueue, "max=", rb_szqueue_max_set, 1);
+ rb_define_method(rb_cSizedQueue, "push", rb_szqueue_push, 1);
+ rb_define_method(rb_cSizedQueue, "pop", rb_szqueue_pop, -1);
+ rb_define_method(rb_cSizedQueue, "num_waiting", rb_szqueue_num_waiting, 0);
+ rb_alias(rb_cSizedQueue, rb_intern("enq"), rb_intern("push"));
+ rb_alias(rb_cSizedQueue, rb_intern("<<"), rb_intern("push"));
+ rb_alias(rb_cSizedQueue, rb_intern("deq"), rb_intern("pop"));
+ rb_alias(rb_cSizedQueue, rb_intern("shift"), rb_intern("pop"));
+
+ rb_provide("thread.rb");
+ ALIAS_GLOBCAL_CONST(ConditionVariable);
+ ALIAS_GLOBCAL_CONST(Queue);
+ ALIAS_GLOBCAL_CONST(SizedQueue);
+}