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}