Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 270 lines (216 sloc) 8.809 kb
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
/* -----------------------------------------------------------------------------
*
* (c) The GHC Team 2001-2005
*
* Tasks
*
* -------------------------------------------------------------------------*/

#ifndef TASK_H
#define TASK_H

#include "GetTime.h"

BEGIN_RTS_PRIVATE

/*
Definition of a Task
--------------------
A task is an OSThread that runs Haskell code. Every OSThread that
runs inside the RTS, whether as a worker created by the RTS or via
an in-call from C to Haskell, has an associated Task. The first
time an OS thread calls into Haskell it is allocated a Task, which
remains until the RTS is shut down.

There is a one-to-one relationship between OSThreads and Tasks.
The Task for an OSThread is kept in thread-local storage, and can
be retrieved at any time using myTask().
In the THREADED_RTS build, multiple Tasks may all be running
Haskell code simultaneously. A task relinquishes its Capability
when it is asked to evaluate an external (C) call.

Ownership of Task
-----------------

The OS thread named in the Task structure has exclusive access to
the structure, as long as it is the running_task of its Capability.
That is, if (task->cap->running_task == task), then task->id owns
the Task. Otherwise the Task is owned by the owner of the parent
data structure on which it is sleeping; for example, if the task is
sleeping on spare_workers field of a Capability, then the owner of the
Capability has access to the Task.

When a task is migrated from sleeping on one Capability to another,
its task->cap field must be modified. When the task wakes up, it
will read the new value of task->cap to find out which Capability
it belongs to. Hence some synchronisation is required on
task->cap, and this is why we have task->lock.

If the Task is not currently owned by task->id, then the thread is
either

(a) waiting on the condition task->cond. The Task is either
(1) a bound Task, the TSO will be on a queue somewhere
(2) a worker task, on the spare_workers queue of task->cap.

(b) making a foreign call. The InCall will be on the
suspended_ccalls list.

We re-establish ownership in each case by respectively

(a) the task is currently blocked in yieldCapability().
This call will return when we have ownership of the Task and
a Capability. The Capability we get might not be the same
as the one we had when we called yieldCapability().
(b) we must call resumeThread(task), which will safely establish
ownership of the Task and a Capability.
*/

// The InCall structure represents either a single in-call from C to
// Haskell, or a worker thread.
typedef struct InCall_ {
    StgTSO * tso; // the bound TSO (or NULL for a worker)

    StgTSO * suspended_tso; // the TSO is stashed here when we
// make a foreign call (NULL otherwise);

    Capability *suspended_cap; // The capability that the
                                // suspended_tso is on, because
                                // we can't read this from the TSO
                                // without owning a Capability in the
                                // first place.

    struct Task_ *task;

    // When a Haskell thread makes a foreign call that re-enters
    // Haskell, we end up with another Task associated with the
    // current thread. We have to remember the whole stack of InCalls
    // associated with the current Task so that we can correctly
    // save & restore the InCall on entry to and exit from Haskell.
    struct InCall_ *prev_stack;

    // Links InCalls onto suspended_ccalls, spare_incalls
    struct InCall_ *prev;
    struct InCall_ *next;
} InCall;

typedef struct Task_ {
#if defined(THREADED_RTS)
    OSThreadId id; // The OS Thread ID of this task

    Condition cond; // used for sleeping & waking up this task
    Mutex lock; // lock for the condition variable

    // this flag tells the task whether it should wait on task->cond
    // or just continue immediately. It's a workaround for the fact
    // that signalling a condition variable doesn't do anything if the
    // thread is already running, but we want it to be sticky.
    rtsBool wakeup;
#endif

    // This points to the Capability that the Task "belongs" to. If
    // the Task owns a Capability, then task->cap points to it. If
    // the task does not own a Capability, then either (a) if the task
    // is a worker, then task->cap points to the Capability it belongs
    // to, or (b) it is returning from a foreign call, then task->cap
    // points to the Capability with the returning_worker queue that this
    // this Task is on.
    //
    // When a task goes to sleep, it may be migrated to a different
    // Capability. Hence, we always check task->cap on wakeup. To
    // syncrhonise between the migrater and the migratee, task->lock
    // must be held when modifying task->cap.
    struct Capability_ *cap;

    // The current top-of-stack InCall
    struct InCall_ *incall;

    nat n_spare_incalls;
    struct InCall_ *spare_incalls;

    rtsBool worker; // == rtsTrue if this is a worker Task
    rtsBool stopped; // this task has stopped or exited Haskell

    SchedulerStatus stat; // return status
    StgClosure ** ret; // return value

    // Stats that we collect about this task
    // ToDo: we probably want to put this in a separate TaskStats
    // structure, so we can share it between multiple Tasks. We don't
    // really want separate stats for each call in a nested chain of
    // foreign->haskell->foreign->haskell calls, but we'll get a
    // separate Task for each of the haskell calls.
    Ticks elapsedtimestart;
    Ticks muttimestart;
    Ticks mut_time;
    Ticks mut_etime;
    Ticks gc_time;
    Ticks gc_etime;

    // Links tasks on the returning_tasks queue of a Capability, and
    // on spare_workers.
    struct Task_ *next;

    // Links tasks on the all_tasks list
    struct Task_ *all_link;

} Task;

INLINE_HEADER rtsBool
isBoundTask (Task *task)
{
    return (task->incall->tso != NULL);
}


// Linked list of all tasks.
//
extern Task *all_tasks;

// Start and stop the task manager.
// Requires: sched_mutex.
//
void initTaskManager (void);
nat freeTaskManager (void);

// Create a new Task for a bound thread
// Requires: sched_mutex.
//
Task *newBoundTask (void);

// The current task is a bound task that is exiting.
// Requires: sched_mutex.
//
void boundTaskExiting (Task *task);

// Notify the task manager that a task has stopped. This is used
// mainly for stats-gathering purposes.
// Requires: sched_mutex.
//
#if defined(THREADED_RTS)
// In the non-threaded RTS, tasks never stop.
void workerTaskStop (Task *task);
#endif

// Record the time spent in this Task.
// This is called by workerTaskStop() but not by boundTaskExiting(),
// because it would impose an extra overhead on call-in.
//
void taskTimeStamp (Task *task);

// Put the task back on the free list, mark it stopped. Used by
// forkProcess().
//
void discardTasksExcept (Task *keep);

// Get the Task associated with the current OS thread (or NULL if none).
//
INLINE_HEADER Task *myTask (void);

#if defined(THREADED_RTS)

// Workers are attached to the supplied Capability. This Capability
// should not currently have a running_task, because the new task
// will become the running_task for that Capability.
// Requires: sched_mutex.
//
void startWorkerTask (Capability *cap);

#endif /* THREADED_RTS */

// -----------------------------------------------------------------------------
// INLINE functions... private from here on down:

// A thread-local-storage key that we can use to get access to the
// current thread's Task structure.
#if defined(THREADED_RTS)
#if defined(linux_HOST_OS) && \
(defined(i386_HOST_ARCH) || defined(x86_64_HOST_ARCH))
#define MYTASK_USE_TLV
extern __thread Task *my_task;
#else
extern ThreadLocalKey currentTaskKey;
#endif
#else
extern Task *my_task;
#endif

//
// myTask() uses thread-local storage to find the Task associated with
// the current OS thread. If the current OS thread has multiple
// Tasks, because it has re-entered the RTS, then the task->prev_stack
// field is used to store the previous Task.
//
INLINE_HEADER Task *
myTask (void)
{
#if defined(THREADED_RTS) && !defined(MYTASK_USE_TLV)
    return getThreadLocalVar(&currentTaskKey);
#else
    return my_task;
#endif
}

INLINE_HEADER void
setMyTask (Task *task)
{
#if defined(THREADED_RTS) && !defined(MYTASK_USE_TLV)
    setThreadLocalVar(&currentTaskKey,task);
#else
    my_task = task;
#endif
}

END_RTS_PRIVATE

#endif /* TASK_H */
Something went wrong with that request. Please try again.