atap 0.1.0

Threadsafe futureless async runtime for macOS
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
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
//! # Runtime
//!
//! `Runtime` manages every event called into it and returns
//! their results as they finish

use crate::{
    RuntimeError,
    constants::{DEAD_KQUEUE_ID, RESTART_BACKOFF, RESTART_LIMIT, RESTART_WINDOW},
    executor::{self, Executor},
    futures::sleep::Sleep,
    futures::task::Task,
    modules::{
        builder::{RuntimeBuilder, TaskBuilder},
        handle_kind::HandleKind,
        input::{self, Standalone},
        int_check::IntCheck,
        join_policy::JoinPolicy,
        pool_stats::PoolStats,
        runtime_status::RuntimeStatus,
        task_handle::TaskHandle,
        tuning::Tuning,
        worker_pool::POOL,
    },
    reactor::Reactor,
};
use std::{
    sync::{
        atomic::{AtomicBool, AtomicI32, Ordering},
        mpsc,
    },
    thread,
    time::Duration,
    time::Instant,
};

/// Whether the runtime has been initialised yet
///
/// Use `SeqCst` operations only as
/// it is important that a `Runtime` only gets
/// initialised once
static INIT: AtomicBool = AtomicBool::new(false);

/// Whether initialisation has finished, successfully or not
static READY: AtomicBool = AtomicBool::new(false);

/// The kqueue id that the `Reactor` watches
///
/// Only use `Relaxed` reads for speed
/// and a single `SeqCst` write at initialisation
/// to ensure everything reads it correctly
static REACTOR_KQUEUE_ID: AtomicI32 = AtomicI32::new(0);

/// Global caller into the runtime
pub struct Runtime;

impl Runtime {
    /// Inits a new runtime
    ///
    /// If a runtime is already initialised, this returns
    /// `AlreadyInit` and changes nothing
    ///
    /// After a `shutdown`, this starts it again. A shutdown still
    /// in progress is waited out first
    ///
    /// Runtimes aren't returned as objects to call methods on
    /// and instead handle all their operations and state internally
    ///
    /// For this reason, Runtimes are threadsafe
    pub fn init() -> Result<(), RuntimeError> {
        Self::init_with(Tuning::new())
    }

    /// Starts a runtime with sizes of your own
    ///
    /// Nothing is started until the chain ends in `init`
    ///
    /// ```no_run
    /// use atap::{Runtime, RuntimeError};
    ///
    /// # fn main() -> Result<(), RuntimeError> {
    /// Runtime::builder().workers_per_core(2).init()?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn builder() -> RuntimeBuilder {
        RuntimeBuilder::new()
    }

    /// Inits a new runtime with these sizes
    ///
    /// The sizes are taken only by the call that starts the
    /// runtime, so one that finds it already running changes
    /// nothing
    pub(crate) fn init_with(tuning: Tuning) -> Result<(), RuntimeError> {
        if INIT.swap(true, Ordering::SeqCst) {
            // Somebody else is part way through, so wait it out
            while !READY.load(Ordering::Acquire) {
                thread::yield_now();
            }

            // Starts it again after a shutdown, and is `AlreadyInit`
            // otherwise
            return Executor::init(tuning);
        }

        let started = init_runtime(tuning);

        // Set whether or not it worked
        READY.store(true, Ordering::Release);

        started
    }

    /// Blocking call
    ///
    /// Used when you need the data
    /// the instant it arrives and
    /// don't mind waiting for it
    ///
    /// Blocking calls can't be cancelled
    /// by any means
    ///
    /// A block inside a task running on a worker still lets that
    /// worker run queued tasks while it waits, the same as a join
    ///
    /// Runs on the calling thread, so it still works if the
    /// manager goes or the runtime is shut down
    #[inline(always)]
    pub fn block<F>(mut task: F) -> F::Output
    where
        F: Task,
        F::Input: Standalone,
    {
        task.give(input::token(), input::standalone());
        task.prepare(input::token());
        let reactor_id = REACTOR_KQUEUE_ID.load(Ordering::Relaxed);

        // IDs are per thread, so 0 can't overlap
        task.execute(input::token(), reactor_id, 0)
    }

    /// Builds a task up before spawning it
    ///
    /// ## Behaviour
    /// Every task that runs on the pool starts here. What it
    /// does is decided by what is chained on before `spawn`:
    /// once now, once later, repeating back to back, repeating
    /// with a gap, on a fixed rate, and bounded by a count or
    /// a deadline or both
    ///
    /// A combination with no meaning doesn't compile, such as
    /// a gap with no repeat or the same bound set twice
    ///
    /// Nothing happens until `spawn` is called
    ///
    /// ```no_run
    /// # use atap::{Runtime, compute::Compute};
    /// # use std::time::Duration;
    /// # let work = Compute::compute(|()| ());
    /// # let gap = Duration::from_secs(1);
    /// Runtime::task(work).priority(200).repeat().every(gap).spawn();
    /// ```
    ///
    /// #### Note
    /// A bare `Runtime::task(t).spawn()` is a task that runs
    /// once, now, at the default priority
    #[inline(always)]
    pub fn task<F>(task: F) -> TaskBuilder<F>
    where
        F: Task,
    {
        TaskBuilder::new(task)
    }

    /// Sleeps the calling thread, accurately
    ///
    /// Shorthand for `Runtime::block(Sleep::sleep(time))`
    ///
    /// ## Returns
    /// The total time it actually took
    ///
    /// #### Note
    /// Precise, so the last stretch is spun rather than slept. Use
    /// `block` with `Sleep::sleep(time).mode(SleepMode::Relaxed)`
    /// for a sleep that never burns a core
    #[inline(always)]
    pub fn sleep(time: Duration) -> Duration {
        Self::block(Sleep::sleep(time))
    }

    /// Waits for every one of a set of tasks
    ///
    /// ## Returns
    /// One result per task, in the order they were given, each
    /// exactly what `join` would have given for that task
    pub fn join_all<T, W, I>(handles: I) -> Vec<Result<T, RuntimeError>>
    where
        I: IntoIterator<Item = TaskHandle<T, W>>,
        T: Clone,
        W: HandleKind,
    {
        handles.into_iter().map(|handle| handle.join()).collect()
    }

    /// Waits for the first of several tasks to settle
    ///
    /// Settled means ready, taken, cancelled or failed. A handle
    /// with no task behind it counts as settled straight away
    ///
    /// ## Returns
    /// The winning handle, and what `policy` said to do about
    /// the rest. Only [`JoinPolicy::PassBack`] gives a `Some`,
    /// and it keeps the order they were given in
    ///
    /// An empty set has no winner, so what comes back is a
    /// handle to no task, and every read on it answers
    /// `NoSuchTask`
    ///
    /// ```no_run
    /// # use atap::{JoinPolicy, Runtime, compute::Compute};
    /// # fn main() -> Result<(), atap::RuntimeError> {
    /// # let handles = vec![Runtime::task(Compute::compute(|()| 1)).spawn()];
    /// let (first, rest) = Runtime::join_first(handles, JoinPolicy::Cancel);
    /// let answer = first.take()?;
    /// # Ok(())
    /// # }
    /// ```
    pub fn join_first<T, W, I>(
        handles: I,
        policy: JoinPolicy,
    ) -> (TaskHandle<T, W>, Option<Vec<TaskHandle<T, W>>>)
    where
        I: IntoIterator<Item = TaskHandle<T, W>>,
        W: HandleKind,
    {
        let mut handles: Vec<TaskHandle<T, W>> = handles.into_iter().collect();

        let ids: Vec<usize> = handles.iter().map(|handle| handle.id()).collect();

        // `None` only for an empty set
        let winner = match executor::join_first(&ids) {
            Some(winner) => winner,
            None => {
                return (
                    TaskHandle::detached(),
                    match policy {
                        JoinPolicy::PassBack => Some(Vec::new()),
                        _ => None,
                    },
                );
            }
        };

        // Removed rather than swapped, so the losers keep their order
        let at = ids.iter().position(|id| *id == winner).unwrap_or_default();

        let first = handles.remove(at);

        match policy {
            JoinPolicy::PassBack => (first, Some(handles)),

            JoinPolicy::Cancel => {
                for handle in handles {
                    handle.cancel();
                }

                (first, None)
            }

            JoinPolicy::Drop => {
                drop(handles);

                (first, None)
            }
        }
    }

    /// Whether the runtime has finished initialising
    ///
    /// #### Note
    /// Says initialisation is over, not that it worked. Use
    /// `status` for whether anything came of it
    pub fn initialised() -> bool {
        READY.load(Ordering::Acquire)
    }

    /// Whether everything is up and nothing has given up
    ///
    /// Shorthand for `Runtime::status().healthy()`
    pub fn healthy() -> bool {
        Self::status().healthy()
    }

    /// What the runtime looks like right now
    ///
    /// #### Note
    /// A snapshot rather than a lock
    pub fn status() -> RuntimeStatus {
        let initialised = Self::initialised();

        // None of these mean anything before an initialisation
        RuntimeStatus::new(
            initialised,
            initialised && executor::shutting_down(),
            initialised && REACTOR_KQUEUE_ID.load(Ordering::Relaxed) != DEAD_KQUEUE_ID,
            initialised && executor::manager_alive(),
            initialised && POOL.live() > 0 && POOL.dead() == 0,
        )
    }

    /// Stops the runtime until the next `init`
    ///
    /// ## Behaviour
    /// Drains rather than aborts. Nothing new gets in, and a
    /// spawn after this settles `Failed` straight away.
    /// Everything already queued still runs, and a task in
    /// flight runs to the end
    ///
    /// Blocks until the pool has nothing left to do and every
    /// thread it started has gone. Anything the drain can't
    /// reach, like a repeat between runs or a socket task waiting
    /// on the network, is failed
    ///
    /// `block` still works during and after a shutdown
    ///
    /// `init` after this starts the runtime again. Handles from
    /// before it keep reading what their tasks ended with
    ///
    /// #### Note
    /// Calling it twice is safe. The second call waits for the
    /// first to finish
    ///
    /// #### Note
    /// Never comes back while a task that never finishes is
    /// still running, so don't call it from inside a spawned task
    pub fn shutdown() {
        executor::shutdown_now();
    }

    /// Gives back the memory behind the unused part of the
    /// task table
    ///
    /// ## Returns
    /// Bytes handed back to the kernel, or `StillInUse` when
    /// the table is too close to the number of tasks alive in
    /// it for any of it to be worth taking
    ///
    /// ## Behaviour
    /// Gives back at most a fifth of the table at a time, keeps
    /// headroom above what is live, and never goes below a
    /// hundred slots. Calling it repeatedly is how it converges
    ///
    /// #### Note
    /// The runtime already does this by itself every few
    /// seconds. This forces a pass
    pub fn trim() -> Result<usize, RuntimeError> {
        executor::trim()
    }

    /// Makes the manager come apart the next `count` times it
    /// goes round its loop
    ///
    /// ## Behaviour
    /// Fewer than the restart limit and the supervisor brings it
    /// back every time. More and it gives up, and everything
    /// waiting on its queue is failed
    ///
    /// #### Note
    /// Only here for the crate's own tests
    #[cfg(feature = "fault-injection")]
    #[doc(hidden)]
    pub fn inject_manager_faults(count: u32) {
        executor::inject_manager_faults(count);
    }

    /// Makes the next `workers` workers and `sleep_threads` sleep
    /// threads to reach a check come apart, as a fault in the runtime
    /// itself would take them down
    ///
    /// ## Behaviour
    /// Some die idle, some holding a task and some part way through
    /// helping
    ///
    /// #### Note
    /// Only here for the crate's own tests
    #[cfg(feature = "fault-injection")]
    #[doc(hidden)]
    pub fn inject_thread_deaths(workers: u32, sleep_threads: u32) {
        crate::modules::faults::owe_thread_deaths(workers, sleep_threads);
        POOL.wake_everyone();
    }

    /// Makes the next `count` attempts to start a thread fail, the way
    /// the kernel refuses one
    ///
    /// #### Note
    /// Only here for the crate's own tests
    #[cfg(feature = "fault-injection")]
    #[doc(hidden)]
    pub fn inject_spawn_refusals(count: u32) {
        crate::modules::faults::owe_spawn_refusals(count);
    }

    /// Limits how many runs deep a worker helps while a task it runs
    /// waits, zero for not at all
    ///
    /// #### Note
    /// Only here for the crate's own tests
    #[cfg(feature = "fault-injection")]
    #[doc(hidden)]
    pub fn inject_help_depth(depth: usize) {
        crate::modules::help::limit_depth(depth);
    }

    /// What the worker pool looks like right now
    ///
    /// #### Note
    /// A snapshot rather than a lock. Every number in it was
    /// true when it was read
    pub fn pool() -> PoolStats {
        POOL.stats()
    }

    /// The kqueue the `Reactor` is watching
    #[inline(always)]
    pub(crate) fn reactor_id() -> i32 {
        REACTOR_KQUEUE_ID.load(Ordering::Relaxed)
    }
}

/// The real non user facing init function
///
/// Called by the `Runtime::init()` method only
fn init_runtime(tuning: Tuning) -> Result<(), RuntimeError> {
    let reactor_id = unsafe { libc::kqueue() }.check()?;

    REACTOR_KQUEUE_ID.store(reactor_id, Ordering::SeqCst);

    thread::spawn(move || {
        let (tx, rx) = mpsc::channel();
        Reactor::init(reactor_id, tx.clone());

        let mut failures = 0;
        let mut started = Instant::now();

        for dead_id in rx {
            if started.elapsed() >= RESTART_WINDOW {
                failures = 0;
            }

            failures += 1;

            if failures > RESTART_LIMIT {
                REACTOR_KQUEUE_ID.store(DEAD_KQUEUE_ID, Ordering::SeqCst);
                let _ = unsafe { libc::close(dead_id) };
                break;
            }

            thread::sleep(RESTART_BACKOFF * failures);

            let new_id = match unsafe { libc::kqueue() }.check() {
                Ok(new_id) => new_id,
                Err(_) => {
                    REACTOR_KQUEUE_ID.store(DEAD_KQUEUE_ID, Ordering::SeqCst);
                    let _ = unsafe { libc::close(dead_id) };
                    break;
                }
            };

            REACTOR_KQUEUE_ID.store(new_id, Ordering::SeqCst);
            Reactor::init(new_id, tx.clone());

            let _ = unsafe { libc::close(dead_id) };
            started = Instant::now();
        }
    });

    // Not spawned, since the kqueue has to exist before `init` returns
    Executor::init(tuning)
}