Skip to main content

dxfeed_graal/
event_queue.rs

1//! Event queues: the events of subscriptions pulled on a thread of the application, without listeners.
2//!
3//! A queue takes the events of the subscriptions attached to it; a poll waits for them and returns them on the calling
4//! thread. An [`EventQueue`] serves one subscription and polls its events as `E`; a [`SharedEventQueue`] serves many,
5//! returns each event with the tag of its subscription, and lends its [`Executor`] to the models, the instrument
6//! profile collector and the promise handlers, whose listeners then run inside the polls, on the polling thread.
7//!
8//! The SDK keeps no copy of the events between the polls: it takes them from the buffers of the subscriptions when a
9//! poll runs. Until then lasting events, such as quotes, are conflated, and the other events wait in a buffer of the
10//! subscription (1000000 events by default, the system property `com.devexperts.qd.impl.matrix.Agent.MaxBufferSize`);
11//! beyond it the oldest ones are dropped and counted (`get_dropped_events`). An endpoint of
12//! [`Role::StreamFeed`](crate::Role::StreamFeed) drops nothing: it stops receiving data until the queue is polled.
13//!
14//! Rules of the SDK:
15//! - Attach a subscription before it gets symbols while it is attached to a feed, and before it gets an executor: a
16//!   subscription with an executor, or one already attached to a queue, is refused with `IllegalStateException`. On a
17//!   subscription that has symbols, add the event listeners before the queue: one added after it is refused.
18//! - Poll a queue from one thread at a time: the polls are serialized, and a snapshot of indexed events is not split
19//!   then.
20//! - A poll inside a listener or a callback of the crate returns [`Error::InCallback`].
21//! - Closing an endpoint does not close its queues and does not end a poll: poll with a finite timeout, or close the
22//!   queue. Close the queue before
23//!   [`DXEndpoint::close_and_await_termination`](crate::DXEndpoint::close_and_await_termination) of the endpoint of an
24//!   attached subscription, which waits while the queue holds events that no poll returned; never call it on the
25//!   polling thread.
26//!
27//! For async code, [`StreamOptions::event_queue`](crate::StreamOptions::event_queue) makes an
28//! [`EventStream`](crate::EventStream) over the queue of its subscription, and [`SharedEventQueue::into_stream`] a
29//! [`TaggedEventStream`](crate::TaggedEventStream) over a shared queue; each is polled on a thread of the stream only
30//! while a task waits for events, and while an endpoint awaits its termination, so that the queue of a stream needs no
31//! close before `close_and_await_termination`.
32//!
33//! With the cargo feature `experimental`, a queue also has a ready signal (`set_ready_signal`): an OS object that the
34//! SDK makes ready when the queue has work, so that an event loop polls with the timeout zero on its own thread.
35//!
36//! ```
37//! use std::time::Duration;
38//!
39//! use dxfeed_graal::event::Quote;
40//! use dxfeed_graal::event_queue::{EventQueue, PollResult};
41//! use dxfeed_graal::{DXEndpoint, Role};
42//!
43//! let endpoint = DXEndpoint::create_with_role(Role::LocalHub)?;
44//! let subscription = endpoint.get_feed().create_subscription::<Quote>()?;
45//! let queue = EventQueue::new(&subscription)?;
46//! subscription.add_symbols(["AAPL"])?;
47//! endpoint.get_publisher().publish_events(&[Quote::new("AAPL").into()])?;
48//! match queue.poll(Some(Duration::from_secs(5)), 0)? {
49//!     PollResult::Events(quotes) => println!("{quotes:?}"),
50//!     PollResult::NoEvents => println!("no quotes yet"),
51//!     PollResult::Closed => println!("the queue is closed"),
52//! }
53//! # Ok::<(), dxfeed_graal::Error>(())
54//! ```
55
56use std::marker::PhantomData;
57use std::ptr;
58use std::time::Duration;
59
60use dxfeed_graal_sys as sys;
61
62use crate::error::{Error, Result};
63use crate::event::sdk_event::{take_event_list, take_owned_list, take_owned_tagged_list, take_tagged_event_list};
64use crate::event::{Event, SubscriptionItem};
65use crate::executor::Executor;
66use crate::handle::Handle;
67use crate::isolate::{self, call, check, with_thread};
68use crate::stream::Source;
69use crate::subscription::DXFeedSubscription;
70
71/// The result of a poll.
72#[derive(Debug, Clone, PartialEq)]
73pub enum PollResult<T> {
74    /// The events that arrived, at least one.
75    Events(T),
76    /// No events arrived before the timeout or the wake-up; the listeners of the models and the collector on the
77    /// queue may have run.
78    NoEvents,
79    /// The queue is closed: every later poll returns this at once.
80    Closed,
81}
82
83/// A queue of the events of one subscription (`EventQueue.create(DXFeedSubscription)`): a poll returns them as `E`, in
84/// their order.
85///
86/// The queue closes when the subscription closes, when [`close`](Self::close) is called and when it is dropped. The
87/// subscription keeps the executor of the queue for its life, also after the queue is closed. The queue can be shared
88/// by threads, but poll it from one thread at a time.
89pub struct EventQueue<E: SubscriptionItem = Event> {
90    handle: Handle<sys::dxfg_v4_event_queue_t>,
91    _item: PhantomData<fn() -> E>,
92}
93
94impl<E: SubscriptionItem> EventQueue<E> {
95    /// Creates a queue of the events of `subscription`.
96    ///
97    /// Create it before the symbols are added to a subscription attached to a feed. A subscription with an executor or
98    /// a queue, or one attached to a feed that has symbols, is [`Error::Java`] of `java.lang.IllegalStateException`;
99    /// on a subscription that has symbols, add its event listeners first.
100    #[doc(alias = "create")]
101    pub fn new(subscription: &DXFeedSubscription<E>) -> Result<Self> {
102        let s = subscription.as_ptr();
103        // SAFETY: the subscription is live; the out-pointer is valid for writes.
104        let handle = Handle::create(|t, out| unsafe { sys::dxfg_v4_EventQueue_new(t, s, out) })?;
105        Ok(EventQueue {
106            handle,
107            _item: PhantomData,
108        })
109    }
110
111    /// Makes the poll in progress, or the next one, return [`PollResult::NoEvents`] at once unless it has events;
112    /// several wake-ups before a poll wake up one poll.
113    #[doc(alias = "wakeUp")]
114    pub fn wake_up(&self) -> Result<()> {
115        wake_up(&self.handle)
116    }
117
118    /// Closes the queue: the poll in progress returns the events it has or [`PollResult::Closed`], and the events that
119    /// no poll returned are dropped and counted. It does not wait for the poll in progress; closing a closed queue
120    /// does nothing.
121    pub fn close(&self) -> Result<()> {
122        close(&self.handle)
123    }
124
125    /// The number of the events of the subscription that were lost, which never decreases: if it grew since the
126    /// previous poll, events were lost before those that the last poll returned.
127    #[doc(alias = "getDroppedEvents")]
128    pub fn get_dropped_events(&self) -> Result<i64> {
129        dropped_events(&self.handle)
130    }
131
132    /// Sets, replaces or removes the ready signal of the queue (`dxfg_v4_EventQueue_setReadySignal`); see
133    /// [`ReadySignal`]. Needs the cargo feature `experimental`.
134    ///
135    /// # Safety
136    ///
137    /// The object of the signal must stay open, and be the same object, until another signal replaces it,
138    /// [`ReadySignal::None`] removes it, [`close`](Self::close) returns on any thread or a poll returns
139    /// [`PollResult::Closed`]: the SDK writes to it until then. Dropping the queue or closing its subscription does not
140    /// count; to stop an event loop, close the queue, and close the object after a poll returns `Closed`.
141    #[cfg(feature = "experimental")]
142    #[doc(alias = "setReadySignal")]
143    pub unsafe fn set_ready_signal(&self, signal: ReadySignal) -> Result<()> {
144        set_ready_signal(&self.handle, signal)
145    }
146}
147
148impl<E: SubscriptionItem> Source for EventQueue<E> {
149    type Item = E;
150
151    fn poll_items(&self, timeout: Option<Duration>, limit: usize) -> Result<PollResult<(Vec<E>, u64)>> {
152        let q = self.handle.as_ptr();
153        let (timeout, limit) = (timeout_millis(timeout), limit_of(limit));
154        let mut list = ptr::null_mut();
155        // SAFETY: the queue is live; the out-pointer is valid for writes.
156        let result = poll(|t| unsafe { sys::dxfg_v4_EventQueue_poll(t, q, timeout, limit, &mut list) })?;
157        Ok(match result {
158            // SAFETY: on events the SDK returned a list that is ours.
159            Polled::Events => PollResult::Events(unsafe { take_owned_list(list) }),
160            Polled::NoEvents => PollResult::NoEvents,
161            Polled::Closed => PollResult::Closed,
162        })
163    }
164
165    fn wake_up(&self) -> Result<()> {
166        wake_up(&self.handle)
167    }
168
169    fn close(&self) -> Result<()> {
170        close(&self.handle)
171    }
172
173    fn get_dropped_events(&self) -> Result<i64> {
174        dropped_events(&self.handle)
175    }
176}
177
178impl<E: SubscriptionItem + TryFrom<Event>> EventQueue<E> {
179    /// Waits for the events of the subscription and returns them; the event listeners of the subscription run in the
180    /// poll, on this thread.
181    ///
182    /// `timeout` is how long to wait: zero returns at once, `None` waits until events arrive, the queue is woken up or
183    /// closed. After the first events the poll only completes the retrieval in progress. `limit` stops the retrieval
184    /// after that many events, `0` for no limit; a batch may exceed it. A failure of the SDK drops the events that the
185    /// poll took, counted in [`get_dropped_events`](Self::get_dropped_events), and the next poll goes on with the next
186    /// ones; an event that the crate cannot copy fails the poll and drops its batch without counting it.
187    ///
188    /// Inside a listener or a callback of the crate it returns [`Error::InCallback`].
189    pub fn poll(&self, timeout: Option<Duration>, limit: usize) -> Result<PollResult<Vec<E>>> {
190        let q = self.handle.as_ptr();
191        let (timeout, limit) = (timeout_millis(timeout), limit_of(limit));
192        let mut list = ptr::null_mut();
193        // SAFETY: the queue is live; the out-pointer is valid for writes.
194        let result = poll(|t| unsafe { sys::dxfg_v4_EventQueue_poll(t, q, timeout, limit, &mut list) })?;
195        Ok(match result {
196            // SAFETY: on events the SDK returned a list that is ours.
197            Polled::Events => events(unsafe { take_event_list(list) }?),
198            Polled::NoEvents => PollResult::NoEvents,
199            Polled::Closed => PollResult::Closed,
200        })
201    }
202}
203
204impl<E: SubscriptionItem> std::fmt::Debug for EventQueue<E> {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        f.debug_struct("EventQueue").finish_non_exhaustive()
207    }
208}
209
210/// A queue of the events of many subscriptions, each attached with a tag (`EventQueue.create()`): a poll returns
211/// each event with the tag of its subscription. Its [`Executor`] runs the work of models, of the instrument profile
212/// collector and of promise handlers inside the polls.
213///
214/// A subscription leaves the queue only by being closed; the queue stays open then. The queue closes when
215/// [`close`](Self::close) is called, or when it and every executor it lent are dropped. The queue can be shared by
216/// threads, but poll it from one thread at a time.
217pub struct SharedEventQueue {
218    handle: Handle<sys::dxfg_v4_event_queue_t>,
219}
220
221impl SharedEventQueue {
222    /// Creates a queue with no subscription.
223    #[doc(alias = "create")]
224    pub fn new() -> Result<Self> {
225        // SAFETY: the out-pointer is valid for writes.
226        let handle = Handle::create(|t, out| unsafe { sys::dxfg_v4_EventQueue_create(t, out) })?;
227        Ok(SharedEventQueue { handle })
228    }
229
230    /// Attaches `subscription`, whose events then come with `tag`; tags need not be unique. A subscription of any
231    /// endpoint may be attached.
232    ///
233    /// Attach it before the symbols are added to a subscription attached to a feed. A subscription with an executor or
234    /// a queue, or one attached to a feed that has symbols, is [`Error::Java`] of `java.lang.IllegalStateException`. A
235    /// closed subscription, or one attached to a closed queue, is attached as left: its events are dropped and counted.
236    /// On a subscription that has symbols, add its event listeners first.
237    pub fn attach<E: SubscriptionItem>(&self, subscription: &DXFeedSubscription<E>, tag: i64) -> Result<()> {
238        let (q, s) = (self.handle.as_ptr(), subscription.as_ptr());
239        // SAFETY: the queue and the subscription are live.
240        call(|t| unsafe { sys::dxfg_v4_EventQueue_attach(t, q, s, tag) })
241    }
242
243    /// The executor of the queue: the work posted to it runs inside the polls, on the polling thread. Give it to the
244    /// models, the collector, the endpoint builder or [`Promise::when_done_async`](crate::Promise::when_done_async).
245    ///
246    /// The executor keeps the queue open as the queue itself does; the executor of a closed queue runs the work at
247    /// once, on the posting thread, without calling the listeners of the application. A subscription that has it as
248    /// its executor gets its events by its listeners, not by the polls. The calls of a model that notify on the
249    /// calling thread (`set_symbol`, `clear`) call its listener there, not in a poll.
250    ///
251    /// For a subscription that has it as its executor, also inherited from an endpoint built with it,
252    /// [`DXFeed::detach_subscription_and_clear`](crate::DXFeed::detach_subscription_and_clear) and
253    /// [`DXEndpoint::close_and_await_termination`](crate::DXEndpoint::close_and_await_termination) wait until a poll
254    /// runs the pending work or the queue is closed: never call them on the polling thread.
255    #[doc(alias = "getExecutor")]
256    pub fn get_executor(&self) -> Result<Executor> {
257        let q = self.handle.as_ptr();
258        // SAFETY: the queue is live; the out-pointer is valid for writes.
259        let handle = Handle::create(|t, out| unsafe { sys::dxfg_v4_EventQueue_getExecutor(t, q, out) })?;
260        Ok(Executor::from_handle(handle))
261    }
262
263    /// Waits for the events of the attached subscriptions and the work of the executor, and returns the events with
264    /// their tags, as [`EventQueue::poll`] does; the listeners of the subscriptions and of the models on the queue run
265    /// in the poll, on this thread. [`PollResult::NoEvents`] also comes when only the work of the executor ran.
266    ///
267    /// Inside a listener or a callback of the crate it returns [`Error::InCallback`].
268    #[doc(alias = "pollTagged")]
269    pub fn poll(&self, timeout: Option<Duration>, limit: usize) -> Result<PollResult<Vec<(i64, Event)>>> {
270        let q = self.handle.as_ptr();
271        let (timeout, limit) = (timeout_millis(timeout), limit_of(limit));
272        let mut list = ptr::null_mut();
273        // SAFETY: the queue is live; the out-pointer is valid for writes.
274        let result = poll(|t| unsafe { sys::dxfg_v4_EventQueue_pollTagged(t, q, timeout, limit, &mut list) })?;
275        Ok(match result {
276            // SAFETY: on events the SDK returned a list that is ours.
277            Polled::Events => events(unsafe { take_tagged_event_list(list) }?),
278            Polled::NoEvents => PollResult::NoEvents,
279            Polled::Closed => PollResult::Closed,
280        })
281    }
282
283    /// Makes the poll in progress, or the next one, return [`PollResult::NoEvents`] at once unless it has events;
284    /// several wake-ups before a poll wake up one poll.
285    #[doc(alias = "wakeUp")]
286    pub fn wake_up(&self) -> Result<()> {
287        wake_up(&self.handle)
288    }
289
290    /// Closes the queue, as [`EventQueue::close`] does; the pending work of the executor runs on this thread without
291    /// calling the listeners of the application, and its later work runs at once on the posting thread.
292    pub fn close(&self) -> Result<()> {
293        close(&self.handle)
294    }
295
296    /// The number of the events of the attached subscriptions that were lost, which never decreases; it is one for
297    /// the whole queue, see [`DXFeedSubscription::get_total_dropped_events`] for each subscription.
298    #[doc(alias = "getDroppedEvents")]
299    pub fn get_dropped_events(&self) -> Result<i64> {
300        dropped_events(&self.handle)
301    }
302
303    /// Sets, replaces or removes the ready signal of the queue, as [`EventQueue::set_ready_signal`] does; the work of
304    /// the executor also signals. Needs the cargo feature `experimental`.
305    ///
306    /// # Safety
307    ///
308    /// As for [`EventQueue::set_ready_signal`]: the object must stay open until another signal replaces it,
309    /// [`ReadySignal::None`] removes it, [`close`](Self::close) returns or a poll returns [`PollResult::Closed`]. A
310    /// live executor of the queue keeps it open, so dropping the queue alone does not stop the writes.
311    #[cfg(feature = "experimental")]
312    #[doc(alias = "setReadySignal")]
313    pub unsafe fn set_ready_signal(&self, signal: ReadySignal) -> Result<()> {
314        set_ready_signal(&self.handle, signal)
315    }
316}
317
318impl Source for SharedEventQueue {
319    type Item = (i64, Event);
320
321    fn poll_items(&self, timeout: Option<Duration>, limit: usize) -> Result<PollResult<(Vec<(i64, Event)>, u64)>> {
322        let q = self.handle.as_ptr();
323        let (timeout, limit) = (timeout_millis(timeout), limit_of(limit));
324        let mut list = ptr::null_mut();
325        // SAFETY: the queue is live; the out-pointer is valid for writes.
326        let result = poll(|t| unsafe { sys::dxfg_v4_EventQueue_pollTagged(t, q, timeout, limit, &mut list) })?;
327        Ok(match result {
328            // SAFETY: on events the SDK returned a list that is ours.
329            Polled::Events => PollResult::Events(unsafe { take_owned_tagged_list(list) }),
330            Polled::NoEvents => PollResult::NoEvents,
331            Polled::Closed => PollResult::Closed,
332        })
333    }
334
335    fn wake_up(&self) -> Result<()> {
336        wake_up(&self.handle)
337    }
338
339    fn close(&self) -> Result<()> {
340        close(&self.handle)
341    }
342
343    fn get_dropped_events(&self) -> Result<i64> {
344        dropped_events(&self.handle)
345    }
346}
347
348/// The ready signal of an event queue: the OS object that the SDK makes ready whenever the queue has work (events, the
349/// work of its executor, a wake-up, the close), so that an event loop, such as `epoll` or
350/// `RegisterWaitForSingleObject`, polls the queue with the timeout zero on its own thread. Needs the cargo feature
351/// `experimental`: the SDK function is experimental and may change in a minor release.
352///
353/// The application creates the object, owns it and closes it. A signal disarms the queue and each poll arms it again,
354/// signalling at once if work remains: reset the object before each poll. A signal without work is possible; its poll
355/// returns [`PollResult::NoEvents`]. A queue has one signal, and the SDK does not write to an old one after
356/// `set_ready_signal` returns; the close signals once more and removes the signal. A signal set on a closed queue is
357/// written once and not kept. An object that the SDK refuses, such as a descriptor that is negative, closed, blocking
358/// or read-only, or a handle that is null or `INVALID_HANDLE_VALUE`, is [`Error::Java`] of
359/// `java.lang.IllegalArgumentException`, and the signal stays as it was. On Windows the value holds a raw handle, so
360/// it is neither `Send` nor `Sync`.
361#[cfg(feature = "experimental")]
362#[derive(Debug, Clone, Copy, PartialEq, Eq)]
363#[non_exhaustive]
364pub enum ReadySignal {
365    /// No signal: removes the signal of the queue.
366    None,
367    /// A non-blocking descriptor open for writing, such as an `eventfd` made with `EFD_NONBLOCK` or the write end of
368    /// a non-blocking pipe: the SDK writes the 8 bytes of the value 1 to it.
369    #[cfg(unix)]
370    Fd(std::os::fd::RawFd),
371    /// A Windows event: the SDK calls `SetEvent` on it.
372    #[cfg(windows)]
373    WindowsEvent(std::os::windows::io::RawHandle),
374}
375
376impl std::fmt::Debug for SharedEventQueue {
377    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
378        f.debug_struct("SharedEventQueue").finish_non_exhaustive()
379    }
380}
381
382/// The outcome of an SDK poll that did not fail.
383enum Polled {
384    Events,
385    NoEvents,
386    Closed,
387}
388
389/// Runs an SDK poll, which returns `0` with events, `DXFG_EVENT_QUEUE_NO_EVENTS`, `DXFG_EVENT_QUEUE_CLOSED`, or `-1`
390/// with a thread exception.
391fn poll(f: impl FnOnce(isolate::Thread) -> i32) -> Result<Polled> {
392    if isolate::in_callback() {
393        return Err(Error::InCallback);
394    }
395    const NO_EVENTS: i32 = sys::DXFG_EVENT_QUEUE_NO_EVENTS as i32;
396    const CLOSED: i32 = sys::DXFG_EVENT_QUEUE_CLOSED as i32;
397    with_thread(|t| match f(t) {
398        NO_EVENTS => Ok(Polled::NoEvents),
399        CLOSED => Ok(Polled::Closed),
400        code => check(t, code).map(|()| Polled::Events),
401    })?
402}
403
404/// The result of a poll that has `events`: the SDK reports no events otherwise, so an empty list is no events.
405fn events<T>(events: Vec<T>) -> PollResult<Vec<T>> {
406    if events.is_empty() {
407        PollResult::NoEvents
408    } else {
409        PollResult::Events(events)
410    }
411}
412
413/// The timeout of an SDK poll: `-1` waits for ever, a part of a millisecond counts as one.
414fn timeout_millis(timeout: Option<Duration>) -> i64 {
415    timeout.map_or(-1, |t| {
416        i64::try_from(t.as_nanos().div_ceil(1_000_000)).unwrap_or(i64::MAX)
417    })
418}
419
420/// The limit of an SDK poll; one beyond `i32` is no stricter than `i32::MAX`.
421fn limit_of(limit: usize) -> i32 {
422    i32::try_from(limit).unwrap_or(i32::MAX)
423}
424
425fn wake_up(handle: &Handle<sys::dxfg_v4_event_queue_t>) -> Result<()> {
426    let q = handle.as_ptr();
427    // SAFETY: the queue is live.
428    call(|t| unsafe { sys::dxfg_v4_EventQueue_wakeUp(t, q) })
429}
430
431fn close(handle: &Handle<sys::dxfg_v4_event_queue_t>) -> Result<()> {
432    let q = handle.as_ptr();
433    // SAFETY: the queue is live.
434    call(|t| unsafe { sys::dxfg_v4_EventQueue_close(t, q) })
435}
436
437#[cfg(feature = "experimental")]
438fn set_ready_signal(handle: &Handle<sys::dxfg_v4_event_queue_t>, signal: ReadySignal) -> Result<()> {
439    let (kind, object) = match signal {
440        ReadySignal::None => (sys::DXFG_V4_READY_SIGNAL_NONE, 0),
441        #[cfg(unix)]
442        ReadySignal::Fd(fd) => (sys::DXFG_V4_READY_SIGNAL_FD, i64::from(fd)),
443        #[cfg(windows)]
444        ReadySignal::WindowsEvent(event) => (sys::DXFG_V4_READY_SIGNAL_WINDOWS_EVENT, event as isize as i64),
445    };
446    let q = handle.as_ptr();
447    // SAFETY: the queue is live; the caller keeps the object open while the SDK writes to it.
448    call(|t| unsafe { sys::dxfg_v4_EventQueue_setReadySignal(t, q, kind, object) })
449}
450
451fn dropped_events(handle: &Handle<sys::dxfg_v4_event_queue_t>) -> Result<i64> {
452    let (q, mut dropped) = (handle.as_ptr(), 0);
453    // SAFETY: the queue is live; the out-pointer is valid for writes.
454    call(|t| unsafe { sys::dxfg_v4_EventQueue_getDroppedEvents(t, q, &mut dropped) })?;
455    Ok(dropped)
456}
457
458#[cfg(test)]
459mod tests {
460    use super::*;
461
462    #[test]
463    fn the_timeout_rounds_up_and_saturates() {
464        assert_eq!(timeout_millis(None), -1);
465        assert_eq!(timeout_millis(Some(Duration::ZERO)), 0);
466        assert_eq!(timeout_millis(Some(Duration::from_micros(1))), 1);
467        assert_eq!(timeout_millis(Some(Duration::from_millis(1500))), 1500);
468        assert_eq!(timeout_millis(Some(Duration::MAX)), i64::MAX);
469        assert_eq!(limit_of(0), 0);
470        assert_eq!(limit_of(usize::MAX), i32::MAX);
471    }
472}