summaryrefslogtreecommitdiff
path: root/include/ruby/fiber/scheduler.h
blob: 8f3d3833303f55d7ddba9d72a16236785b281195 (plain)
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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
#ifndef RUBY_FIBER_SCHEDULER_H                       /*-*-C++-*-vi:se ft=cpp:*/
#define RUBY_FIBER_SCHEDULER_H
/**
 * @file
 * @author     Ruby developers <ruby-core@ruby-lang.org>
 * @copyright  This  file  is   a  part  of  the   programming  language  Ruby.
 *             Permission  is hereby  granted,  to  either redistribute  and/or
 *             modify this file, provided that  the conditions mentioned in the
 *             file COPYING are met.  Consult the file for details.
 * @brief      Scheduler APIs.
 */
#include "ruby/internal/config.h"

#include <errno.h>

#ifdef STDC_HEADERS
#include <stddef.h> /* size_t */
#endif

#include "ruby/ruby.h"
#include "ruby/internal/dllexport.h"
#include "ruby/internal/arithmetic.h"

RBIMPL_SYMBOL_EXPORT_BEGIN()

#define RUBY_FIBER_SCHEDULER_VERSION 2

struct timeval;

/**
 * Wrap a `ssize_t` and `int errno` into a single `VALUE`. This interface should
 * be used to safely capture results from system calls  like `read` and `write`.
 *
 * You should use `rb_fiber_scheduler_io_result_apply` to unpack the result of
 * this value and update `int errno`.
 *
 * You should not directly try to interpret the result value as it is considered
 * an opaque representation. However, the general representation is an integer
 * in the range of `[-int errno, size_t size]`. Linux generally restricts the
 * result of system calls like `read` and `write` to `<= 2^31` which means this
 * will typically fit within a single FIXNUM.
 *
 * @param[in]  result   The result of the system call.
 * @param[in]  error    The value of `errno`.
 * @return              A `VALUE` which contains the result and/or errno.
 */
static inline VALUE
rb_fiber_scheduler_io_result(ssize_t result, int error)
{
    if (result == -1) {
        return RB_INT2NUM(-error);
    }
    else {
        return RB_SIZE2NUM(result);
    }
}

/**
 * Apply an io result to the local thread, returning the value of the original
 * system call that created it and updating `int errno`.
 *
 * You should not directly try to interpret the result value as it is considered
 * an opaque representation.
 *
 * @param[in]  result   The `VALUE` which contains an errno and/or result size.
 * @post                Updates `int errno` with the value if negative.
 * @return              The original result of the system call.
 */
static inline ssize_t
rb_fiber_scheduler_io_result_apply(VALUE result)
{
    if (RB_FIXNUM_P(result) && RB_NUM2INT(result) < 0) {
        errno = -RB_NUM2INT(result);
        return -1;
    }
    else {
        return RB_NUM2SIZE(result);
    }
}

/**
 * Queries the  current scheduler of  the current  thread that is  calling this
 * function.
 *
 * @retval  RUBY_Qnil  No scheduler has  been set so far to  this thread (which
 *                     is the default).
 * @retval  otherwise  The scheduler that  was last set for  the current thread
 *                     with rb_fiber_scheduler_set().
 */
VALUE rb_fiber_scheduler_get(void);

/**
 * Destructively assigns  the passed  scheduler to that  of the  current thread
 * that is calling this function.  If the scheduler is set, non-blocking fibers
 * (created by `Fiber.new` with `blocking: false`, or by `Fiber.schedule`) call
 * that scheduler's  hook methods on  potentially blocking operations,  and the
 * current  thread  will  call  scheduler's  `#close`  method  on  finalisation
 * (allowing  the  scheduler  to  properly  manage  all  non-finished  fibers).
 * `scheduler`   can   be   an   object   of   any   class   corresponding   to
 * `Fiber::Scheduler` interface. Its implementation is up to the user.
 *
 * @param[in]  scheduler     The scheduler to set.
 * @exception  rb_eArgError  `scheduler` does not conform the interface.
 * @post       Current thread's scheduler is `scheduler`.
 */
VALUE rb_fiber_scheduler_set(VALUE scheduler);

/**
 * Identical to rb_fiber_scheduler_get(), except it also returns ::RUBY_Qnil in
 * case of a blocking fiber.  As blocking fibers do not participate schedulers'
 * scheduling this function can be handy.
 *
 * @retval  RUBY_Qnil  No scheduler is in effect.
 * @retval  otherwise  The scheduler that is in effect, if any.
 */
VALUE rb_fiber_scheduler_current(void);

/**
 * Identical to rb_fiber_scheduler_current(), except it queries for that of the
 * passed thread instead of the implicit current one.
 *
 * @param[in]  thread         Target thread.
 * @exception  rb_eTypeError  `thread` is not a thread.
 * @retval     RUBY_Qnil      No scheduler is in effect in `thread`.
 * @retval     otherwise      The scheduler that is in effect in `thread`.
 */
VALUE rb_fiber_scheduler_current_for_thread(VALUE thread);

/**
 * Converts the passed timeout to an expression that rb_fiber_scheduler_block()
 * etc. expects.
 *
 * @param[in]  timeout    A duration (can be `NULL`).
 * @retval     RUBY_Qnil  No timeout (blocks indefinitely).
 * @retval     otherwise  A timeout object.
 */
VALUE rb_fiber_scheduler_make_timeout(struct timeval *timeout);

/**
 * Closes the passed scheduler object.  This expects the scheduler to wait for
 * all fibers.  Thus the scheduler's main loop tends to start here.
 *
 * @param[in]  scheduler  Target scheduler.
 * @return     What `scheduler.close` returns.
 */
VALUE rb_fiber_scheduler_close(VALUE scheduler);

/**
 * Non-blocking  `sleep`.  Depending  on  scheduler  implementation,  this  for
 * instance switches to another fiber etc.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  duration   Passed as-is to `scheduler.kernel_sleep`.
 * @return     What `scheduler.kernel_sleep` returns.
 */
VALUE rb_fiber_scheduler_kernel_sleep(VALUE scheduler, VALUE duration);

/**
 * Identical to rb_fiber_scheduler_kernel_sleep(), except  it can pass multiple
 * arguments.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  argc       Number of objects of `argv`.
 * @param[in]  argv       Passed as-is to `scheduler.kernel_sleep`
 * @return     What `scheduler.kernel_sleep` returns.
 */
VALUE rb_fiber_scheduler_kernel_sleepv(VALUE scheduler, int argc, VALUE * argv);

/* Description TBW */
#if 0
VALUE rb_fiber_scheduler_timeout_after(VALUE scheduler, VALUE timeout, VALUE exception, VALUE message);
VALUE rb_fiber_scheduler_timeout_afterv(VALUE scheduler, int argc, VALUE * argv);
int rb_fiber_scheduler_supports_process_wait(VALUE scheduler);
#endif

/**
 * Non-blocking `waitpid`.  Depending  on  scheduler  implementation, this  for
 * instance switches to another fiber etc.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  pid        Process ID to wait.
 * @param[in]  flags      Wait flags, e.g. `WUNTRACED`.
 * @return     What `scheduler.process_wait` returns.
 */
VALUE rb_fiber_scheduler_process_wait(VALUE scheduler, rb_pid_t pid, int flags);

/**
 * Non-blocking  wait  for  the  passed   "blocker",  which  is   for  instance
 * `Thread.join` or `Mutex.lock`.  Depending  on scheduler implementation, this
 * for instance switches to another fiber etc.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  blocker    What blocks the current fiber.
 * @param[in]  timeout    Numeric timeout.
 * @return     What `scheduler.block` returns.
 */
VALUE rb_fiber_scheduler_block(VALUE scheduler, VALUE blocker, VALUE timeout);

/**
 * Wakes up a fiber previously blocked using rb_fiber_scheduler_block().
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  blocker    What was awaited for.
 * @param[in]  fiber      What to unblock.
 * @return     What `scheduler.unblock` returns.
 */
VALUE rb_fiber_scheduler_unblock(VALUE scheduler, VALUE blocker, VALUE fiber);

/**
 * Non-blocking version of rb_io_wait().  Depending on scheduler
 * implementation, this for instance switches to another fiber etc.
 *
 * The  "events" here  is a  Ruby level  integer, which  is an  OR-ed value  of
 * `IO::READABLE`, `IO::WRITABLE`, and `IO::PRIORITY`.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  io         An io object to wait.
 * @param[in]  events     An integer set of interests.
 * @param[in]  timeout    Numeric timeout.
 * @return     What `scheduler.io_wait` returns.
 */
VALUE rb_fiber_scheduler_io_wait(VALUE scheduler, VALUE io, VALUE events, VALUE timeout);

/**
 * Non-blocking  wait until the passed  IO  is ready  for reading.   This is  a
 * special  case   of  rb_fiber_scheduler_io_wait(),  where  the   interest  is
 * `IO::READABLE` and timeout is never.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  io         An io object to wait.
 * @return     What `scheduler.io_wait` returns.
 */
VALUE rb_fiber_scheduler_io_wait_readable(VALUE scheduler, VALUE io);

/**
 * Non-blocking  wait until  the passed  IO  is ready  for writing.   This is a
 * special  case   of  rb_fiber_scheduler_io_wait(),  where  the   interest  is
 * `IO::WRITABLE` and timeout is never.
 *
 * @param[in]  scheduler  Target scheduler.
 * @param[in]  io         An io object to wait.
 * @return     What `scheduler.io_wait` returns.
 */
VALUE rb_fiber_scheduler_io_wait_writable(VALUE scheduler, VALUE io);

/**
 * Non-blocking version of `IO.select`.
 *
 * It's possible that this will be emulated using a thread, so you should not
 * rely on it for high performance.
 *
 * @param[in]  scheduler    Target scheduler.
 * @param[in]  readables    An array of readable objects.
 * @param[in]  writables    An array of writable objects.
 * @param[in]  exceptables  An array of objects that might encounter exceptional conditions.
 * @param[in]  timeout      Numeric timeout or nil.
 * @return     What `scheduler.io_select` returns, normally a 3-tuple of arrays of ready objects.
 */
VALUE rb_fiber_scheduler_io_select(VALUE scheduler, VALUE readables, VALUE writables, VALUE exceptables, VALUE timeout);

/**
 * Non-blocking version of `IO.select`, `argv` variant.
 */
VALUE rb_fiber_scheduler_io_selectv(VALUE scheduler, int argc, VALUE *argv);

/**
 * Non-blocking read from the passed IO.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to read from.
 * @param[in]   buffer       The buffer to read to.
 * @param[in]   length       The minimum number of bytes to read.
 * @param[in]   offset       The offset in the buffer to read from.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 * @return      otherwise    What `scheduler.io_read` returns `[-errno, size]`.
 */
VALUE rb_fiber_scheduler_io_read(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset);

/**
 * Non-blocking write to the passed IO.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to write to.
 * @param[in]   buffer       The buffer to write from.
 * @param[in]   length       The minimum number of bytes to write.
 * @param[in]   offset       The offset in the buffer to write from.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 * @return      otherwise    What `scheduler.io_write` returns `[-errno, size]`.
 */
VALUE rb_fiber_scheduler_io_write(VALUE scheduler, VALUE io, VALUE buffer, size_t length, size_t offset);

/**
 * Non-blocking read from the passed IO at the specified offset.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to read from.
 * @param[in]   from         The offset to read from.
 * @param[in]   buffer       The buffer to read to.
 * @param[in]   length       The minimum number of bytes to read.
 * @param[in]   offset       The offset in the buffer to read to.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 * @return      otherwise    What `scheduler.io_read` returns.
 */
VALUE rb_fiber_scheduler_io_pread(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset);

/**
 * Non-blocking write to the passed IO at the specified offset.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to write to.
 * @param[in]   from         The offset to write to.
 * @param[in]   buffer       The buffer to write from.
 * @param[in]   length       The minimum number of bytes to write.
 * @param[in]   offset       The offset in the buffer to write from.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 * @return      otherwise    What `scheduler.io_write` returns.
 */
VALUE rb_fiber_scheduler_io_pwrite(VALUE scheduler, VALUE io, rb_off_t from, VALUE buffer, size_t length, size_t offset);

/**
 * Non-blocking read from the passed IO using a native buffer.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to read from.
 * @param[in]   base         The memory to read to.
 * @param[in]   size         Size of the memory.
 * @param[in]   length       The minimum number of bytes to read.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 * @return      otherwise    What `scheduler.io_read` returns.
 */
VALUE rb_fiber_scheduler_io_read_memory(VALUE scheduler, VALUE io, void *base, size_t size, size_t length);

/**
 * Non-blocking write to the passed IO using a native buffer.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to write to.
 * @param[in]   base         The memory to write from.
 * @param[in]   size         Size of the memory.
 * @param[in]   length       The minimum number of bytes to write.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 * @return      otherwise    What `scheduler.io_write` returns.
 */
VALUE rb_fiber_scheduler_io_write_memory(VALUE scheduler, VALUE io, const void *base, size_t size, size_t length);

/**
 * Non-blocking pread from the passed IO using a native buffer.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to read from.
 * @param[in]   from         The offset to read from.
 * @param[in]   base         The memory to read to.
 * @param[in]   size         Size of the memory.
 * @param[in]   length       The minimum number of bytes to read.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_read`.
 * @return      otherwise    What `scheduler.io_read` returns.
 */
VALUE rb_fiber_scheduler_io_pread_memory(VALUE scheduler, VALUE io, rb_off_t from, void *base, size_t size, size_t length);

/**
 * Non-blocking pwrite to the passed IO using a native buffer.
 *
 * @param[in]   scheduler    Target scheduler.
 * @param[in]   io           An io object to write to.
 * @param[in]   from         The offset to write from.
 * @param[in]   base         The memory to write from.
 * @param[in]   size         Size of the memory.
 * @param[in]   length       The minimum number of bytes to write.
 * @retval      RUBY_Qundef  `scheduler` doesn't have `#io_write`.
 * @return      otherwise    What `scheduler.io_write` returns.
 */
VALUE rb_fiber_scheduler_io_pwrite_memory(VALUE scheduler, VALUE io, rb_off_t from, const void *base, size_t size, size_t length);

/**
 * Non-blocking close the given IO.
 *
 * @param[in]  scheduler    Target scheduler.
 * @param[in]  io           An io object to close.
 * @retval     RUBY_Qundef  `scheduler` doesn't have `#io_close`.
 * @return     otherwise    What `scheduler.io_close` returns.
 */
VALUE rb_fiber_scheduler_io_close(VALUE scheduler, VALUE io);

/**
 * Non-blocking DNS lookup.
 *
 * @param[in]  scheduler    Target scheduler.
 * @param[in]  hostname     A host name to query.
 * @retval     RUBY_Qundef  `scheduler` doesn't have `#address_resolve`.
 * @return     otherwise    What `scheduler.address_resolve` returns.
 */
VALUE rb_fiber_scheduler_address_resolve(VALUE scheduler, VALUE hostname);

/**
 * Create and schedule a non-blocking fiber.
 *
 */
VALUE rb_fiber_scheduler_fiber(VALUE scheduler, int argc, VALUE *argv, int kw_splat);

RBIMPL_SYMBOL_EXPORT_END()

#endif /* RUBY_FIBER_SCHEDULER_H */