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
|
/* -----------------------------------------------------------------------------
*
* (c) The GHC Team 1998-2020
*
* Prototypes for functions in IOManager.c and elsewhere
*
* Hooks for the I/O subsystem(s) that are called from other parts of the RTS.
*
* There are several different I/O subsystem implementations (aka I/O managers),
* for different platforms (notably Windows vs others), and for the threaded vs
* non-threaded RTS. These implementations all need hooks into other parts of
* the RTS, such as startup/shutdown, the scheduler and other special features.
*
* To keep things comprehensible, all the hooks used by all the different I/O
* subsystem implementations are centralised here. Not all implementations use
* all hooks.
*
* -------------------------------------------------------------------------*/
#pragma once
#include "BeginPrivate.h"
#include "sm/GC.h" // for evac_fn
#include "posix/Select.h" // for LowResTime TODO: switch to normal Time
/* The per-capability data structures belonging to the I/O manager.
*
* It can be accessed as cap->iomgr.
*
* The content of the structure is defined conditionally so it is different for
* each I/O manager implementation.
*
* TODO: once the content of this struct is genuinely private, and not shared
* with other parts of the RTS, then it can be made opaque, so the content is
* known only to the I/O manager and not the rest of the RTS.
*/
typedef struct {
#if defined(THREADED_RTS)
#if !defined(mingw32_HOST_OS)
/* Control FD for the MIO manager for this capability */
int control_fd;
#endif
#else // !defined(THREADED_RTS)
/* Thread queue for threads blocked on I/O completion.
* Used by the select() and Win32 MIO I/O managers. It is not used by
* the WinIO I/O manager, though it remains defined in this case.
*/
StgTSO *blocked_queue_hd;
StgTSO *blocked_queue_tl;
/* Thread queue for threads blocked on timeouts.
* Used by the select() I/O manager only. It is grossly inefficient, like
* everything else to do with the select() I/O manager.
*
* TODO: It is not used by any of the Windows I/O managers, though it
* remains defined for them. This is an oddity that should be resolved.
*/
StgTSO *sleeping_queue;
#endif
} CapIOManager;
/* Allocate and initialise the per-capability CapIOManager that lives in each
* Capability. It is called from initCapability, via initScheduler,
* via hs_init_ghc.
*/
void initCapabilityIOManager(CapIOManager **iomgr);
/* Init hook: called from hs_init_ghc, very late in the startup after almost
* everything else is done.
*/
void initIOManager(void);
/* Init hook: called from forkProcess in the child process on the surviving
* capability.
*
* Note that this is synchronous and can run Haskell code, so can change the
* given cap.
*/
void initIOManagerAfterFork(/* inout */ Capability **pcap);
/* TODO: rationalise initIOManager and initIOManagerAfterFork into a single
per-capability init function.
*/
/* Shutdown hooks: called from hs_exit_ before and after the scheduler exits.
*
* The stopIOManager is also called many times (once per-capability) within the
* scheduler shutdown (but only in threaded mode). This is despite the fact that
* stopIOManager shuts down the I/O manager for all capabilities.
* FIXME: this is accidentally quadratic and confusing.
*/
void stopIOManager(void);
void exitIOManager(bool wait_threads);
/* Wakeup hook: called from the scheduler's wakeUpRts (currently only in
* threaded mode).
*
* The I/O manager can be blocked waiting on I/O or timers. Sometimes there are
* other external events where we need to wake up the I/O manager and return
* to the schedulr.
*
* At the moment, all the non-threaded I/O managers will do this automagically
* since a signal will interrupt any waiting system calls, so at the moment
* the implementation for the non-threaded I/O managers does nothing.
*
* For the I/O managers in threaded mode, this arranges to unblock the I/O
* manager if it waa blocked waiting.
*/
void wakeupIOManager(void);
/* GC hook: mark any per-capability GC roots the I/O manager uses.
*/
void markCapabilityIOManager(evac_fn evac, void *user, CapIOManager *iomgr);
#if !defined(THREADED_RTS)
/* Add a thread to the end of the queue of threads blocked on I/O.
*
* This is used by the select() and the Windows MIO non-threaded I/O manager
* implementation.
*/
void appendToIOBlockedQueue(Capability *cap, StgTSO *tso);
/* Insert a thread into the queue of threads blocked on timers.
*
* This is used by the select() I/O manager implementation only.
*
* The sleeping queue is defined for other non-threaded I/O managers but not
* used. This is a wart that should be excised.
*/
void insertIntoSleepingQueue(Capability *cap, StgTSO *tso, LowResTime target);
#endif
/* Check to see if there are any pending timeouts or I/O operations
* in progress with the I/O manager.
*
* This is used by the scheduler as part of deadlock-detection, and the
* "context switch as often as possible" test.
*/
INLINE_HEADER bool anyPendingTimeoutsOrIO(CapIOManager *iomgr);
#if !defined(THREADED_RTS)
/* Check whether there is any completed I/O or expired timers. If so,
* process the competions as appropriate, which will typically cause some
* waiting threads to be woken up.
*
* Called from schedule() both *before* and *after* scheduleDetectDeadlock().
*
* Defined in posix/Select.c
* or win32/AwaitEvent.c
*/
void awaitEvent(Capability *cap, bool wait);
#endif
/* Pedantic warning cleanliness
*/
#if !defined(THREADED_RTS) && defined(mingw32_HOST_OS)
#define USED_IF_NOT_THREADS_AND_MINGW32
#else
#define USED_IF_NOT_THREADS_AND_MINGW32 STG_UNUSED
#endif
#if defined(THREADED_RTS) && !defined(mingw32_HOST_OS)
#define USED_IF_THREADS_AND_NOT_MINGW32
#else
#define USED_IF_THREADS_AND_NOT_MINGW32 STG_UNUSED
#endif
/* -----------------------------------------------------------------------------
* INLINE functions... private from here on down.
*
* Some of these hooks are performance sensitive so parts of them are
* implemented here so they can be inlined.
* -----------------------------------------------------------------------------
*/
INLINE_HEADER bool anyPendingTimeoutsOrIO(CapIOManager *iomgr USED_IF_NOT_THREADS)
{
#if defined(THREADED_RTS)
/* For the purpose of the scheduler, the threaded I/O managers never have
pending I/O or timers. Of course in reality they do, but they're
managed via other primitives that the scheduler can see into (threads,
MVars and foreign blocking calls).
*/
return false;
#else
#if defined(mingw32_HOST_OS)
/* The MIO I/O manager uses the blocked_queue, while the WinIO does not.
Note: the latter fact makes this test useless for the WinIO I/O manager,
and is the probable cause of the complication in the scheduler with
having to call awaitEvent in multiple places.
None of the Windows I/O managers use the sleeping_queue
*/
return (iomgr->blocked_queue_hd != END_TSO_QUEUE);
#else
/* The select() I/O manager uses the blocked_queue and the sleeping_queue.
*/
return (iomgr->blocked_queue_hd != END_TSO_QUEUE)
|| (iomgr->sleeping_queue != END_TSO_QUEUE);
#endif
#endif
}
#include "EndPrivate.h"
|