[svn:parrot] r48680 - in branches/gsoc_threads: include/parrot src src/gc src/interp src/io src/ops src/pmc
Chandon at svn.parrot.org
Chandon at svn.parrot.org
Thu Aug 26 23:06:39 UTC 2010
Author: Chandon
Date: Thu Aug 26 23:06:38 2010
New Revision: 48680
URL: https://trac.parrot.org/parrot/changeset/48680
Log:
[gsoc_threads] Can now kill blocked threads.
Also, now with less completely wrong code.
Modified:
branches/gsoc_threads/include/parrot/interpreter.h
branches/gsoc_threads/include/parrot/scheduler.h
branches/gsoc_threads/include/parrot/threads.h
branches/gsoc_threads/src/gc/mark_sweep.c
branches/gsoc_threads/src/interp/inter_create.c
branches/gsoc_threads/src/interp/inter_misc.c
branches/gsoc_threads/src/io/unix.c
branches/gsoc_threads/src/ops/core_ops.c
branches/gsoc_threads/src/ops/experimental.ops
branches/gsoc_threads/src/pmc/pmclist.pmc
branches/gsoc_threads/src/pmc/task.pmc
branches/gsoc_threads/src/scheduler.c
branches/gsoc_threads/src/threads.c
Modified: branches/gsoc_threads/include/parrot/interpreter.h
==============================================================================
--- branches/gsoc_threads/include/parrot/interpreter.h Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/include/parrot/interpreter.h Thu Aug 26 23:06:38 2010 (r48680)
@@ -270,7 +270,6 @@
UINTVAL last_alarm; /* has an alarm triggered? */
FLOATVAL quantum_done; /* expiration of current quantum */
- PMC *current_task; /* there's always one running task */
Parrot_mutex interp_lock; /* Enforce one running thread per interp */
Modified: branches/gsoc_threads/include/parrot/scheduler.h
==============================================================================
--- branches/gsoc_threads/include/parrot/scheduler.h Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/include/parrot/scheduler.h Thu Aug 26 23:06:38 2010 (r48680)
@@ -132,6 +132,10 @@
__attribute__nonnull__(1)
__attribute__nonnull__(2);
+PARROT_CANNOT_RETURN_NULL
+PMC* Parrot_task_current(PARROT_INTERP)
+ __attribute__nonnull__(1);
+
#define ASSERT_ARGS_Parrot_cx_begin_execution __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp) \
, PARROT_ASSERT_ARG(main) \
@@ -189,6 +193,8 @@
#define ASSERT_ARGS_Parrot_cx_schedule_alarm __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp) \
, PARROT_ASSERT_ARG(alarm))
+#define ASSERT_ARGS_Parrot_task_current __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
+ PARROT_ASSERT_ARG(interp))
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */
/* HEADERIZER END: src/scheduler.c */
Modified: branches/gsoc_threads/include/parrot/threads.h
==============================================================================
--- branches/gsoc_threads/include/parrot/threads.h Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/include/parrot/threads.h Thu Aug 26 23:06:38 2010 (r48680)
@@ -28,6 +28,7 @@
Parrot_cond cvar;
void *lo_var_ptr; /* Full range of stack for GC scan */
void *hi_var_ptr;
+ PMC *cur_task;
} Thread_info;
typedef struct Thread_table {
@@ -65,6 +66,9 @@
/* HEADERIZER BEGIN: src/threads.c */
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */
+void Parrot_check_if_task_killed(PARROT_INTERP)
+ __attribute__nonnull__(1);
+
void Parrot_threads_block(PARROT_INTERP, ARGOUT(INTVAL *tidx))
__attribute__nonnull__(1)
__attribute__nonnull__(2)
@@ -82,6 +86,9 @@
INTVAL Parrot_threads_current(PARROT_INTERP)
__attribute__nonnull__(1);
+void Parrot_threads_gc_mark(PARROT_INTERP)
+ __attribute__nonnull__(1);
+
void Parrot_threads_idle(PARROT_INTERP, INTVAL tidx)
__attribute__nonnull__(1);
@@ -108,13 +115,22 @@
void Parrot_threads_set_signal(PARROT_INTERP)
__attribute__nonnull__(1);
+void Parrot_threads_setup_signal_handler(PARROT_INTERP)
+ __attribute__nonnull__(1);
+
void Parrot_threads_spawn(PARROT_INTERP)
__attribute__nonnull__(1);
+void Parrot_threads_task_killed(PARROT_INTERP, INTVAL tidx)
+ __attribute__nonnull__(1);
+
+void Parrot_threads_task_killed_handler(NULLOK(int sig_number));
void Parrot_threads_unblock(PARROT_INTERP, ARGIN(INTVAL *tidx_ptr))
__attribute__nonnull__(1)
__attribute__nonnull__(2);
+#define ASSERT_ARGS_Parrot_check_if_task_killed __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
+ PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_block __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp) \
, PARROT_ASSERT_ARG(tidx))
@@ -127,6 +143,8 @@
PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_current __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp))
+#define ASSERT_ARGS_Parrot_threads_gc_mark __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
+ PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_idle __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_init __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
@@ -143,8 +161,15 @@
PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_set_signal __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp))
+#define ASSERT_ARGS_Parrot_threads_setup_signal_handler \
+ __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
+ PARROT_ASSERT_ARG(interp))
#define ASSERT_ARGS_Parrot_threads_spawn __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp))
+#define ASSERT_ARGS_Parrot_threads_task_killed __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
+ PARROT_ASSERT_ARG(interp))
+#define ASSERT_ARGS_Parrot_threads_task_killed_handler \
+ __attribute__unused__ int _ASSERT_ARGS_CHECK = (0)
#define ASSERT_ARGS_Parrot_threads_unblock __attribute__unused__ int _ASSERT_ARGS_CHECK = (\
PARROT_ASSERT_ARG(interp) \
, PARROT_ASSERT_ARG(tidx_ptr))
Modified: branches/gsoc_threads/src/gc/mark_sweep.c
==============================================================================
--- branches/gsoc_threads/src/gc/mark_sweep.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/gc/mark_sweep.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -159,6 +159,7 @@
{
ASSERT_ARGS(Parrot_gc_trace_root)
PObj *obj;
+ PMC *tmp;
/* note: adding locals here did cause increased GC runs */
mark_context_start();
@@ -195,9 +196,9 @@
/* mark the root_namespace */
Parrot_gc_mark_PMC_alive(interp, interp->root_namespace);
- /* mark the concurrency scheduler and current task */
+ /* mark the concurrency scheduler and tasks */
Parrot_gc_mark_PMC_alive(interp, interp->scheduler);
- Parrot_gc_mark_PMC_alive(interp, interp->current_task);
+ Parrot_threads_gc_mark(interp);
/* s. packfile.c */
mark_const_subs(interp);
Modified: branches/gsoc_threads/src/interp/inter_create.c
==============================================================================
--- branches/gsoc_threads/src/interp/inter_create.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/interp/inter_create.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -165,8 +165,6 @@
? parent->gc_sys->sys_type
: PARROT_GC_DEFAULT_TYPE;
- interp->current_task = PMCNULL;
-
/* Done. Return and be done with it */
return interp;
}
Modified: branches/gsoc_threads/src/interp/inter_misc.c
==============================================================================
--- branches/gsoc_threads/src/interp/inter_misc.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/interp/inter_misc.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -268,7 +268,7 @@
result = Parrot_pcc_get_lex_pad(interp, CURRENT_CONTEXT(interp));
break;
case CURRENT_TASK:
- result = interp->current_task;
+ result = Parrot_task_current(interp);
break;
default: /* or a warning only? */
Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_UNIMPLEMENTED,
Modified: branches/gsoc_threads/src/io/unix.c
==============================================================================
--- branches/gsoc_threads/src/io/unix.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/io/unix.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -529,6 +529,7 @@
else if (bytes < 0) {
switch (errno) {
case EINTR:
+ Parrot_check_if_task_killed(interp);
continue;
default:
s->bufused = s->strlen = 0;
Modified: branches/gsoc_threads/src/ops/core_ops.c
==============================================================================
--- branches/gsoc_threads/src/ops/core_ops.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/ops/core_ops.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -26164,7 +26164,7 @@
Parrot_receive_p(opcode_t *cur_opcode, PARROT_INTERP) {
const Parrot_Context * const CUR_CTX = Parrot_pcc_get_context_struct(interp, interp->ctx);
opcode_t *const dest = cur_opcode + 2;
- PMC *cur_task = interp->current_task;
+ PMC *cur_task = Parrot_task_current(interp);
Parrot_Task_attributes *tdata = PARROT_TASK(cur_task);
int msg_count = VTABLE_get_integer(interp, tdata->mailbox);
@@ -26172,7 +26172,7 @@
PREG(1) = VTABLE_shift_pmc(interp, tdata->mailbox);return (opcode_t *)dest;
}
else {
- TASK_recv_block_SET(interp->current_task);
+ TASK_recv_block_SET(cur_task);
(void) Parrot_cx_stop_task(interp, cur_opcode);return (opcode_t *)0;
}
Modified: branches/gsoc_threads/src/ops/experimental.ops
==============================================================================
--- branches/gsoc_threads/src/ops/experimental.ops Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/ops/experimental.ops Thu Aug 26 23:06:38 2010 (r48680)
@@ -428,7 +428,7 @@
op receive(out PMC) {
opcode_t *const dest = expr NEXT();
- PMC *cur_task = interp->current_task;
+ PMC *cur_task = Parrot_task_current(interp);
Parrot_Task_attributes *tdata = PARROT_TASK(cur_task);
int msg_count = VTABLE_get_integer(interp, tdata->mailbox);
@@ -437,7 +437,7 @@
goto ADDRESS(dest);
}
else {
- TASK_recv_block_SET(interp->current_task);
+ TASK_recv_block_SET(cur_task);
(void) Parrot_cx_stop_task(interp, cur_opcode);
goto ADDRESS(0);
}
Modified: branches/gsoc_threads/src/pmc/pmclist.pmc
==============================================================================
--- branches/gsoc_threads/src/pmc/pmclist.pmc Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/pmc/pmclist.pmc Thu Aug 26 23:06:38 2010 (r48680)
@@ -484,11 +484,11 @@
=back
-=head2 Auxiliar functions
+=head2 Auxiliary functions
=over 4
-=item C(void Parrot_pmc_list_insert_by_num(PARROT_INTERP, PMC* list, PMC* value)
+=item C<void Parrot_pmc_list_insert_by_number(PARROT_INTERP, PMC *list, PMC *value) >
Insert an item into a sorted list by its num value.
@@ -497,7 +497,8 @@
PARROT_EXPORT
void
-Parrot_pmc_list_insert_by_number(PARROT_INTERP, PMC *list, PMC *value) {
+Parrot_pmc_list_insert_by_number(PARROT_INTERP, PMC *list, PMC *value)
+{
void *tmp;
PMC_List_Item *item;
PMC_List_Item *head;
Modified: branches/gsoc_threads/src/pmc/task.pmc
==============================================================================
--- branches/gsoc_threads/src/pmc/task.pmc Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/pmc/task.pmc Thu Aug 26 23:06:38 2010 (r48680)
@@ -32,8 +32,10 @@
ATTR PMC *code; /* An (optional) code for the task */
ATTR PMC *data; /* Additional data for the task */
ATTR INTVAL killed; /* Dead tasks don't get run */
+ ATTR INTVAL tidx; /* Which thread to signal if killed */
ATTR PMC *mailbox; /* List of incoming messages */
ATTR PMC *waiters; /* Tasks waiting on this one */
+ ATTR Parrot_jump_buff abort_jump; /* Jump buffer to abort task */
/*
@@ -58,6 +60,7 @@
core_struct->data = PMCNULL;
core_struct->interp = INTERP;
core_struct->killed = 0;
+ core_struct->tidx = -1;
core_struct->mailbox = Parrot_pmc_new(interp, enum_class_PMCList);
core_struct->waiters = Parrot_pmc_new(interp, enum_class_ResizablePMCArray);
@@ -147,6 +150,10 @@
/* If a task is pre-empted, this will be set again. */
TASK_in_preempt_CLEAR(SELF);
+ if (setjmp(task->abort_jump)) {
+ /* do nothing, we're back where we want to be */
+ }
+
if (!(task->killed || PMC_IS_NULL(task->code))) {
/* Add the task to the set of active Tasks */
PMC *task_id = Parrot_pmc_new(interp, enum_class_Integer);
@@ -155,15 +162,17 @@
TASK_active_SET(SELF);
/* Actually run the task */
+ task->tidx = Parrot_threads_current(interp);
if (PMC_IS_NULL(task->data)) {
Parrot_ext_call(interp, task->code, "->");
}
else {
Parrot_ext_call(interp, task->code, "P->", task->data);
}
+ task->tidx = -1;
}
- if (!TASK_in_preempt_TEST(SELF)) {
+ if (task->killed || !TASK_in_preempt_TEST(SELF)) {
/* The task is done. */
/* Remove it from the set of active Tasks */
@@ -424,6 +433,10 @@
METHOD kill() {
Parrot_Task_attributes *tdata = PARROT_TASK(SELF);
tdata->killed = 1;
+
+ if (tdata->tidx >= 0) {
+ Parrot_threads_task_killed(interp, tdata->tidx);
+ }
}
}
Modified: branches/gsoc_threads/src/scheduler.c
==============================================================================
--- branches/gsoc_threads/src/scheduler.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/scheduler.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -52,6 +52,9 @@
/* Don't modify between HEADERIZER BEGIN / HEADERIZER END. Your changes will be lost. */
/* HEADERIZER END: static */
+
+static int enable_scheduling = 0;
+
/*
=head2 Scheduler Interface Functions
@@ -115,6 +118,8 @@
PMC* main_task = Parrot_pmc_new(interp, enum_class_Task);
Parrot_Task_attributes *tdata = PARROT_TASK(main_task);
+ enable_scheduling = 1;
+
tdata->code = main;
tdata->data = argv;
@@ -167,16 +172,19 @@
FLOATVAL time_now = Parrot_floatval_time();
opcode_t *dest;
- interp->current_task = VTABLE_shift_pmc(interp, sched->task_queue);
+ PMC *task = VTABLE_shift_pmc(interp, sched->task_queue);
+ INTVAL tidx = Parrot_threads_current(interp);
- if (!VTABLE_isa(interp, interp->current_task, CONST_STRING(interp, "Task")))
+ if (!VTABLE_isa(interp, task, CONST_STRING(interp, "Task")))
Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_INVALID_OPERATION,
"Found a non-Task in the task queue.\n");
+ interp->thread_table->threads[tidx].cur_task = task;
+
interp->quantum_done = time_now + PARROT_TASK_SWITCH_QUANTUM;
Parrot_alarm_set(interp->quantum_done);
- Parrot_ext_call(interp, interp->current_task, "->");
+ Parrot_ext_call(interp, task, "->");
}
else {
Parrot_alarm_now();
@@ -243,13 +251,8 @@
/* A task switch will only work in the outer runloop of a fully
booted Parrot. In a Parrot that hasn't called begin_execution,
or in a nested runloop, we silently ignore task switches. */
- if (interp->current_task && interp->current_runloop_level <= 1)
+ if (enable_scheduling && interp->current_runloop_level <= 1)
return Parrot_cx_preempt_task(interp, scheduler, next);
-
-#ifdef ARBITRARY_DEBUG_STATEMENT
- fprintf(stderr, "Should have exited runloop. Didn't (%lx, %ld)\n",
- interp->current_task, interp->current_runloop_level);
-#endif
}
return next;
@@ -291,13 +294,13 @@
{
ASSERT_ARGS(Parrot_cx_stop_task)
- PMC *task = interp->current_task;
+ PMC *task = Parrot_task_current(interp);
Parrot_Task_attributes *tdata = PARROT_TASK(task);
PMC *cont = Parrot_pmc_new(interp, enum_class_Continuation);
VTABLE_set_pointer(interp, cont, next);
- if (PMC_IS_NULL(task) || !VTABLE_isa(interp, interp->current_task, CONST_STRING(interp, "Task")))
+ if (PMC_IS_NULL(task) || !VTABLE_isa(interp, task, CONST_STRING(interp, "Task")))
Parrot_ex_throw_from_c_args(interp, NULL, EXCEPTION_INVALID_OPERATION,
"Attempt to stop invalid interp->current_task.\n");
@@ -327,7 +330,6 @@
PMC* task = Parrot_cx_stop_task(interp, next);
VTABLE_push_pmc(interp, sched->task_queue, task);
- interp->current_task = PMCNULL;
return (opcode_t*) 0;
}
@@ -451,6 +453,26 @@
/*
+=item C<PMC* Parrot_task_current(PARROT_INTERP)>
+
+Returns the task that is currently running.
+
+=cut
+
+*/
+
+PARROT_CANNOT_RETURN_NULL
+PMC*
+Parrot_task_current(PARROT_INTERP)
+{
+ ASSERT_ARGS(Parrot_task_current)
+ INTVAL tidx = Parrot_threads_current(interp);
+ return interp->thread_table->threads[tidx].cur_task;
+}
+
+
+/*
+
=item C<void Parrot_cx_request_suspend_for_gc(PARROT_INTERP)>
Tell the scheduler to suspend for GC at the next safe pause.
@@ -700,7 +722,7 @@
PMC *alarm = Parrot_pmc_new(interp, enum_class_Alarm);
Parrot_Alarm_attributes *adata = PARROT_ALARM(alarm);
- PMC *task = interp->current_task;
+ PMC *task = Parrot_task_current(interp);
Parrot_Task_attributes *tdata = PARROT_TASK(task);
PMC *cont = Parrot_pmc_new(interp, enum_class_Continuation);
Modified: branches/gsoc_threads/src/threads.c
==============================================================================
--- branches/gsoc_threads/src/threads.c Thu Aug 26 23:00:30 2010 (r48679)
+++ branches/gsoc_threads/src/threads.c Thu Aug 26 23:06:38 2010 (r48680)
@@ -21,6 +21,7 @@
#include "parrot/threads.h"
#include "parrot/alarm.h"
#include "pmc/pmc_scheduler.h"
+#include "pmc/pmc_task.h"
/* HEADERIZER HFILE: include/parrot/threads.h */
@@ -63,6 +64,8 @@
THREAD_STATE_SET(interp, 0, INITIALIZED);
COND_INIT(tbl->threads[0].cvar);
+ Parrot_threads_setup_signal_handler(interp);
+
LOCK_INTERP(interp);
}
@@ -115,6 +118,8 @@
Parrot_alarm_mask(interp);
*tidx = args->idx;
+ Parrot_threads_setup_signal_handler(interp);
+
/* Yay stack scanning */
LOCK(interp->thread_lock);
tbl->threads[*tidx].lo_var_ptr = &tidx;
@@ -567,6 +572,115 @@
/*
+=item C<void Parrot_threads_task_killed(PARROT_INTERP, INTVAL tidx)>
+
+Send a signal to a thread notifying it that its active task has been killed.
+
+=cut
+
+*/
+
+void
+Parrot_threads_task_killed(PARROT_INTERP, INTVAL tidx)
+{
+ ASSERT_ARGS(Parrot_threads_task_killed)
+
+ Thread_table *tbl = interp->thread_table;
+ pthread_kill(tbl->threads[tidx].id, SIGUSR2);
+}
+
+/*
+
+=item C<void Parrot_threads_task_killed_handler(int sig_number)>
+
+Signal handler that does the stuff when the thing happens.
+
+=cut
+
+*/
+
+void
+Parrot_threads_task_killed_handler(SHIM(int sig_number))
+{
+ ASSERT_ARGS(Parrot_threads_task_killed_handler)
+
+ /* interrupt_blocking_system_calls_from_a_comment(); */
+}
+
+/*
+
+=item C<void Parrot_threads_setup_signal_handler(PARROT_INTERP)>
+
+Prepare the current thread to handle a signal notifying it that its active
+task has been killed.
+
+=cut
+
+*/
+
+void
+Parrot_threads_setup_signal_handler(PARROT_INTERP)
+{
+ ASSERT_ARGS(Parrot_threads_setup_signal_handler)
+
+ struct sigaction sa;
+ memset(&sa, 0, sizeof (struct sigaction));
+ sa.sa_handler = Parrot_threads_task_killed_handler;
+ sa.sa_flags = ~SA_RESTART;
+
+ if (sigaction(SIGUSR2, &sa, 0) == -1) {
+ perror("sigaction failed in Parrot_threads_setup-signal_handler");
+ exit(EXIT_FAILURE);
+ }
+}
+
+/*
+
+=item C<void Parrot_check_if_task_killed(PARROT_INTERP)>
+
+If the current task has been killed, longjmp back to the task
+entry point.
+
+=cut
+
+*/
+
+void
+Parrot_check_if_task_killed(PARROT_INTERP)
+{
+ ASSERT_ARGS(Parrot_check_if_task_killed)
+
+ PMC *current_task = Parrot_task_current(interp);
+ Parrot_Task_attributes *const tdata = PARROT_TASK(current_task);
+
+ if (tdata->killed) {
+ longjmp(tdata->abort_jump, 1);
+ }
+}
+
+/*
+
+=item C<void Parrot_threads_gc_mark(PARROT_INTERP)>
+
+Marks any PMCs in the thread table alive.
+
+=cut
+
+*/
+
+void
+Parrot_threads_gc_mark(PARROT_INTERP)
+{
+ ASSERT_ARGS(Parrot_threads_gc_mark)
+ Thread_table *tbl = interp->thread_table;
+ INTVAL i;
+ for (i = 0; i < tbl->count; ++i) {
+ Parrot_gc_mark_PMC_alive(interp, tbl->threads[i].cur_task);
+ }
+}
+
+/*
+
=back
=head1 SEE ALSO
More information about the parrot-commits
mailing list