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
//!Raw Timer

use core::{time, task};
use core::future::Future;

use crate::state::TimerState;

///Timer
///
///## Common implementations:
///
///- Windows uses thread pooled timer
///- Apple systems uses dispatch source API
///- Posix compatible `timer_create`, available on major Posix-compliant systems. Depends on availability of `siginfo_t::si_value` method.
///- Wasm uses Web API `SetTimeout`
///- Dummy timer is used  when no implementation is available. Panics when used.
///
///## Usage
///
///```no_run
///use async_timer::timer::{Timer, new_timer};
///
///use core::time;
///use core::pin::Pin;
///
///async fn do_something() {
///    let mut work = new_timer(time::Duration::from_secs(2));
///    assert!(!work.is_ticking()); //Timer starts only on initial poll
///    assert!(!work.is_expired());
///    Pin::new(&mut work).await; //Remember await consumes future, and we'd prefer to avoid that in order to re-use timer
///    assert!(work.is_expired());
///
///}
///```
pub trait Timer: Send + Sync + Unpin + Future<Output=()> {
    ///Creates new instance
    fn new(timeout: time::Duration) -> Self;

    ///Returns whether timer is ongoing.
    ///
    ///Note that if it returns `false` it doesn't mean that `is_expired` will return `true`
    ///as initially timer may not be armed.
    fn is_ticking(&self) -> bool;

    ///Returns whether timer has expired.
    fn is_expired(&self) -> bool;

    ///Restarts timer with new timeout value.
    fn restart(&mut self, timeout: time::Duration);

    ///Cancels timer, if it is still ongoing.
    fn cancel(&mut self);
}

///Describes timer interface that doesn't require async event loop.
///
///In most cases these timers are implemented by OS calling back a provided callback.
///
///As notification is not done via event loop, timer has to store callback in its own state.
///
///Whenever async timer relies on async event loop to handle notifications
///
///## Usage
///
///```
///use async_timer::timer::{Timer, SyncTimer, new_sync_timer};
///
///use core::sync::atomic::{AtomicBool, Ordering};
///use core::time;
///
///use std::thread;
///
///static EXPIRED: AtomicBool = AtomicBool::new(false);
///fn on_expire() {
///    EXPIRED.store(true, Ordering::Release);
///}
///
///let mut work = new_sync_timer(time::Duration::from_secs(1));
///assert!(!work.is_ticking());
///assert!(!work.is_expired());
///
///work.init(|state| state.register(on_expire as fn()));
///work.tick();
///
///assert!(work.is_ticking());
///assert!(!work.is_expired());
///thread::sleep(time::Duration::from_millis(1250)); //timer is not necessary expires immediately
///
///assert!(work.is_expired());
///assert!(EXPIRED.load(Ordering::Acquire));
///```
///
pub trait SyncTimer: Timer {
    ///Initializes timer state, performing initial arming and allowing to access `TimerState`
    ///during initialization
    ///
    ///The state can be used to register callback and check whether timer has notified user using
    ///configured callback
    ///
    ///If `Timer` is already armed, then `TimerState` is granted as it is.
    fn init<R, F: Fn(&TimerState) -> R>(&mut self, init: F) -> R;

    ///Ticks timer.
    ///
    ///If timer is not started yet, starts returning false.
    ///Otherwise performs necessary actions, if any, to drive timer
    ///and returns whether it is expired.
    ///
    ///Default implementation initializes timer state, if necessary, and
    ///returns whether timer has expired or not.
    #[inline(always)]
    fn tick(&mut self) -> bool {
        self.init(|state| state.is_done())
    }
}

#[inline(always)]
fn poll_sync<T: SyncTimer>(timer: &mut T, ctx: &mut task::Context) -> task::Poll<()> {
    timer.init(|state| {
        state.register(ctx.waker());
        match state.is_done() {
            true => task::Poll::Ready(()),
            false => task::Poll::Pending
        }
    })
}

#[cfg(windows)]
mod win;
#[cfg(windows)]
pub use win::WinTimer;
#[cfg(windows)]
///Platform alias to Windows timer
pub type Platform = win::WinTimer;
#[cfg(windows)]
///Platform alias to Windows timer
pub type SyncPlatform = win::WinTimer;

#[cfg(all(feature = "tokio02", any(target_os = "linux", target_os = "android")))]
mod timer_fd;
#[cfg(all(feature = "tokio02", any(target_os = "linux", target_os = "android")))]
pub use timer_fd::TimerFd;

#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
mod posix;
#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
pub use posix::PosixTimer;
#[cfg(all(not(feature = "tokio02"), not(any(target_os = "macos", target_os = "ios")), unix))]
///Platform alias to POSIX timer
pub type Platform = posix::PosixTimer;
#[cfg(all(feature = "tokio02", any(target_os = "linux", target_os = "android")))]
///Platform alias to Linux `timerfd` Timer
pub type Platform = timer_fd::TimerFd;
#[cfg(all(unix, not(any(target_os = "macos", target_os = "ios"))))]
///Platform alias to POSIX Timer
pub type SyncPlatform = posix::PosixTimer;

#[cfg(all(feature = "tokio02", any(target_os = "bitrig", target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd")))]
mod kqueue;
#[cfg(all(feature = "tokio02", any(target_os = "bitrig", target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd")))]
pub use kqueue::KqueueTimer;
#[cfg(any(target_os = "macos", target_os = "ios"))]
mod apple;
#[cfg(any(target_os = "macos", target_os = "ios"))]
pub use apple::AppleTimer;
#[cfg(all(not(feature = "tokio02"), any(target_os = "macos", target_os = "ios")))]
///Platform alias to Apple Dispatch timer
pub type Platform = apple::AppleTimer;
#[cfg(all(feature = "tokio02", any(target_os = "bitrig", target_os = "dragonfly", target_os = "freebsd", target_os = "ios", target_os = "macos", target_os = "netbsd", target_os = "openbsd")))]
///Platform Alias to `kqueue` based Timer
pub type Platform = kqueue::KqueueTimer;
#[cfg(any(target_os = "macos", target_os = "ios"))]
///Platform Alias to `kqueue` based Timer
pub type SyncPlatform = apple::AppleTimer;

#[cfg(target_arch = "wasm32")]
mod web;
#[cfg(target_arch = "wasm32")]
pub use web::WebTimer;
#[cfg(target_arch = "wasm32")]
///Platform alias to WASM Timer
pub type Platform = web::WebTimer;
#[cfg(target_arch = "wasm32")]
///Platform alias to WASM Timer
pub type SyncPlatform = web::WebTimer;

mod dummy;
pub use dummy::DummyTimer;
#[cfg(not(any(windows, target_arch = "wasm32", unix)))]
///Platform alias to Dummy Timer as no OS implementation is available.
pub type Platform = dummy::DummyTimer;
#[cfg(not(any(windows, target_arch = "wasm32", unix)))]
///Platform alias to Dummy Timer as no OS implementation is available.
pub type SyncPlatform = dummy::DummyTimer;

#[inline]
///Creates new timer, timer type depends on platform.
pub const fn new_timer(timeout: time::Duration) -> Platform {
    Platform::new(timeout)
}

#[inline]
///Creates new timer, which always implements `SyncTimer`
pub const fn new_sync_timer(timeout: time::Duration) -> SyncPlatform {
    SyncPlatform::new(timeout)
}