Skip to main content

dxfeed_graal/
lib.rs

1//! Safe Rust API for dxFeed market data over the dxFeed Graal Native SDK.
2//!
3//! The project is in alpha: the API may change in any release. It covers the dxFeed Java API 3.357 wherever the
4//! SDK exports it; the README lists the Java members that wait for their wrappers or for new SDK exports.
5//!
6//! The names follow the dxFeed Java API: [`DXEndpoint`], [`DXFeed`], [`DXFeedSubscription`], [`DXPublisher`], with
7//! methods in snake case: `addEventListener` is [`add_event_listener`](DXFeedSubscription::add_event_listener) and
8//! `getFeed` is [`get_feed`](DXEndpoint::get_feed). The Java names are search aliases in the documentation.
9//!
10//! # Example
11//!
12//! Print the quotes of a symbol until Enter is pressed:
13//!
14//! ```no_run
15//! use dxfeed_graal::DXEndpoint;
16//! use dxfeed_graal::event::Quote;
17//!
18//! fn main() -> dxfeed_graal::Result<()> {
19//!     let endpoint = DXEndpoint::create()?;
20//!     let subscription = endpoint.get_feed().create_subscription::<Quote>()?;
21//!     subscription.add_event_listener(|quotes| {
22//!         for quote in quotes {
23//!             println!("{quote}");
24//!         }
25//!     })?;
26//!     subscription.add_symbols(["AAPL"])?;
27//!     endpoint.connect("demo.dxfeed.com:7300")?;
28//!     std::io::stdin().read_line(&mut String::new()).ok();
29//!     endpoint.close_and_await_termination()
30//! }
31//! ```
32//!
33//! A quote prints as Java prints it: `Quote{AAPL, eventTime=0, time=20241004-093000.000, timeNanoPart=0, ...}`.
34//! A subscription to several event types, [`DXFeed::create_subscription_with_types`], gives its listeners an
35//! [`EventRef`] to match. All 27 event types of the SDK are mapped; see [`event`].
36//!
37//! The last event of a [`LastingEvent`](event::LastingEvent) type, such as a quote, comes from the local cache with
38//! [`DXFeed::get_last_event_if_subscribed`] and [`DXFeed::get_last_event`], or from the uplink with
39//! [`DXFeed::get_last_event_promise`], whose [`Promise`] is waited for with a timeout:
40//!
41//! ```no_run
42//! use std::time::Duration;
43//!
44//! use dxfeed_graal::DXEndpoint;
45//! use dxfeed_graal::event::Quote;
46//!
47//! fn main() -> dxfeed_graal::Result<()> {
48//!     let endpoint = DXEndpoint::create()?;
49//!     endpoint.connect("demo.dxfeed.com:7300")?;
50//!     let quote = endpoint.get_feed().get_last_event_promise::<Quote>("AAPL")?.wait(Duration::from_secs(10))?;
51//!     println!("{quote}");
52//!     endpoint.close_and_await_termination()
53//! }
54//! ```
55//!
56//! The default feed of the process, [`DXFeed::get_instance`], connects to the address in the `dxfeed.address` system
57//! property or in the `dxfeed.properties` file. System properties come from the code, from the
58//! `dxfeed.system.properties` file and from the `DXFEED_*` environment variables (see [`system`]):
59//!
60//! ```no_run
61//! use dxfeed_graal::event::Quote;
62//! use dxfeed_graal::{DXFeed, system};
63//!
64//! fn main() -> dxfeed_graal::Result<()> {
65//!     system::set_property("dxfeed.address", "demo.dxfeed.com:7300")?;
66//!     let subscription = DXFeed::get_instance()?.create_subscription::<Quote>()?;
67//!     subscription.add_event_listener(|quotes| quotes.for_each(|quote| println!("{quote}")))?;
68//!     subscription.add_symbols(["AAPL"])?;
69//!     std::io::stdin().read_line(&mut String::new()).ok();
70//!     Ok(())
71//! }
72//! ```
73//!
74//! # How-to
75//!
76//! The common tasks have short examples on the items that do them; the `samples` directory of the repository has
77//! complete programs.
78//!
79//! - Subscribe, add and change symbols, close; several event types at once: [`DXFeedSubscription`].
80//! - Events from a time on, such as candles or time and sales: [`DXFeedTimeSeriesSubscription`]; a history in one
81//!   request: [`DXFeed::get_time_series_promise`].
82//! - The orders of one source, all the symbols, a time-series symbol: [`Symbol`].
83//! - Candle symbols with a period, a price, an exchange or a session: [`CandleSymbol`](event::CandleSymbol).
84//! - Read the fields of events, keep copies, use event flags: [`event`]; the main fields of
85//!   [`Quote`](event::Quote), [`Trade`](event::Trade), [`TimeAndSale`](event::TimeAndSale), [`Order`](event::Order),
86//!   [`Candle`](event::Candle) and [`Series`](event::Series).
87//! - The last event of a symbol and other requests: [`Promise`], several at once: [`Promises`]; in async code: the
88//!   [Async](#async) section.
89//! - Conflate events or limit their batches: [`SubscriptionController`].
90//! - Order books: [`OrderBookModel`](model::OrderBookModel), [`MarketDepthModel`](model::MarketDepthModel) and their
91//!   lists, [`ObservableListModel`](model::ObservableListModel).
92//! - Whole transactions and snapshots: [`IndexedTxModel`](model::IndexedTxModel),
93//!   [`TimeSeriesTxModel`](model::TimeSeriesTxModel), [`IndexedEventTxModel`](model::IndexedEventTxModel); live
94//!   lists: [`IndexedEventModel`](model::IndexedEventModel), [`TimeSeriesEventModel`](model::TimeSeriesEventModel).
95//! - Publish events: [`DXPublisher`]; poll events without listeners: [`event_queue`].
96//!
97//! # Threads
98//!
99//! The SDK runs in a GraalVM isolate that the crate creates on first use and never tears down. A thread is attached to
100//! it on its first call and detached when it exits. All types are `Send` and `Sync`.
101//!
102//! # Listeners
103//!
104//! A listener's closure runs on a thread of the endpoint's executor, its own threads by default or an [`Executor`]
105//! thread pool, or inside [`ManualExecutor::process_all_pending`]. The calls of one listener do not overlap. The events
106//! are valid during the call only; copy what you keep with `to_owned()`. A panic in a closure is caught and goes to
107//! [`ErrorManager`].
108//!
109//! Without listeners, an [event queue](event_queue) gives the events of subscriptions to a thread of the application,
110//! which polls them; the listeners of its subscriptions, and of the models and the collector on its executor, then run
111//! inside the polls.
112//!
113//! Add listeners before symbols. Removing a listener or dropping its [`DXFeedSubscription`] waits for its running
114//! calls, except inside a callback, and so does dropping the last owner of an endpoint with state listeners. So do not
115//! hold a lock that a closure takes while doing that from another thread.
116//!
117//! # Async
118//!
119//! [`Promise`] and [`AnyOf`] implement [`IntoFuture`], so async code awaits them instead of
120//! blocking a thread in [`wait`](Promise::wait). The futures work on any executor (tokio, async-std, smol or another);
121//! the crate depends on no runtime and needs no cargo feature for them:
122//!
123//! ```no_run
124//! use dxfeed_graal::event::Quote;
125//! use dxfeed_graal::{DXFeed, Result};
126//!
127//! async fn last_quote(feed: &DXFeed) -> Result<Quote> {
128//!     feed.get_last_event_promise::<Quote>("AAPL")?.await
129//! }
130//! ```
131//!
132//! A [`PromiseFuture`] keeps its promise: [`cancel`](PromiseFuture::cancel) makes it ready at once, so a timer of the
133//! runtime that cancels it is the async timeout. Dropping the future does not cancel the request, closing the endpoint
134//! leaves a pending request pending, and [`system::shutdown`] makes pending futures ready with [`Error::ShutDown`]. A
135//! worker thread of the runtime that polls a future is attached to the isolate, as any thread that calls the SDK is.
136//!
137//! A subscription turns into an [`EventStream`] of owned events with
138//! [`into_stream`](DXFeedSubscription::into_stream), before its symbols are added:
139//!
140//! ```no_run
141//! use dxfeed_graal::DXFeed;
142//! use dxfeed_graal::event::Quote;
143//!
144//! async fn print_quotes(feed: &DXFeed) -> dxfeed_graal::Result<()> {
145//!     let mut quotes = feed.create_subscription::<Quote>()?.into_stream()?;
146//!     quotes.subscription().add_symbols(["AAPL"])?;
147//!     while let Some(quote) = quotes.next().await {
148//!         println!("{quote}");
149//!     }
150//!     Ok(())
151//! }
152//! ```
153//!
154//! Its buffer drops the oldest events when it is full; with [`StreamOptions::event_queue`] the events stay in the SDK
155//! until the consumer waits for them, and the SDK keeps the last quote of each symbol meanwhile ([`StreamOptions`]).
156//! The stream ends when it or its subscription is closed, when its endpoints are closed and on [`system::shutdown`].
157//! The cargo feature `stream` implements `futures_core::Stream` for it.
158//!
159//! A [`SharedEventQueue`](event_queue::SharedEventQueue) turns into a [`TaggedEventStream`] with
160//! [`into_stream`](event_queue::SharedEventQueue::into_stream): it yields the events of all the subscriptions attached
161//! to the queue with their tags, takes them only while the consumer waits, as an event-queue stream does, and runs the
162//! work of the executor of the queue meanwhile. It ends when it or its queue is closed and on [`system::shutdown`].
163//!
164//! The models stream whole items into a [`ModelStream`](model::ModelStream), built in place of their listener:
165//! `build_stream` of the builders of [`IndexedTxModel`](model::IndexedTxModel) and
166//! [`TimeSeriesTxModel`](model::TimeSeriesTxModel) yields a [`Transaction`](model::Transaction) per transaction or
167//! snapshot, and `build_stream` of [`MarketDepthModel`](model::MarketDepthModel) or
168//! [`OrderBookModel::into_stream`](model::OrderBookModel::into_stream) yields [`BookSides`](model::BookSides), the
169//! whole book after a change. A model stream never splits an item: when its consumer lags, a snapshot drops the
170//! buffered transactions of its source and a book replaces the buffered book, and otherwise the buffer grows. It ends
171//! as an event stream does.
172//!
173//! # Errors
174//!
175//! Every fallible call returns [`Result`]. A Java exception becomes [`Error::Java`] with its class, message, stack
176//! trace and causes; invalid arguments, such as strings with NUL bytes, are rejected before any SDK call. A wait that
177//! times out returns [`Error::Timeout`].
178//!
179//! # Closing and exit
180//!
181//! Close an endpoint with [`DXEndpoint::close_and_await_termination`], which waits for the notifications already
182//! scheduled, or with [`DXEndpoint::close`], which does not; dropping its last handle closes it as well. The process
183//! may also exit with open endpoints and running listeners: from the moment it starts exiting, listeners are no longer
184//! called and destructors no longer call the SDK.
185//!
186//! [`system::shutdown`] is an advanced function with no counterpart in the other dxFeed APIs: it stops all listeners
187//! and closes all endpoints of the process, the default one included; afterwards nothing new can be created.
188//!
189//! # Deployment
190//!
191//! The SDK library is linked dynamically and must be found at run time; see the README of the repository.
192#![warn(missing_docs)]
193
194mod auth_token;
195mod connector;
196mod endpoint;
197mod error;
198mod error_manager;
199pub mod event;
200pub mod event_queue;
201mod executor;
202mod feed;
203#[cfg(feature = "experimental")]
204pub mod feed_filter;
205pub mod glossary;
206mod handle;
207pub mod history;
208pub mod ipf;
209mod isolate;
210mod listener;
211pub mod logging;
212pub mod model;
213mod observable;
214pub mod ondemand;
215pub mod orcs;
216mod promise;
217mod publisher;
218pub mod qds;
219pub mod schedule;
220mod stream;
221mod subscription;
222mod subscription_controller;
223mod symbol;
224pub mod system;
225mod terminate;
226mod time_format;
227mod time_period;
228mod time_zone;
229
230pub use auth_token::AuthToken;
231#[cfg(feature = "experimental")]
232pub use connector::{ConnectionError, ConnectionErrorCategory};
233pub use connector::{MessageConnector, MessageConnectorKind, MessageConnectorState, MessageConnectorStatistics};
234pub use endpoint::{DXEndpoint, DXEndpointBuilder, Role, State};
235pub use error::{Error, JavaException, JavaExceptionClass, Result};
236pub use error_manager::{ErrorInfo, ErrorManager};
237#[doc(hidden)]
238pub use event::sdk_event::live_events;
239pub use event::{Event, EventKind, EventRef, EventType, Events, SubscriptionItem};
240pub use executor::{Executor, ManualExecutor};
241pub use feed::DXFeed;
242#[doc(hidden)]
243pub use handle::live_handles;
244pub use observable::{Observable, ObservableSubscription, ObservableSubscriptionChangeListener};
245pub use promise::{AnyOf, AnyOfFuture, Promise, PromiseFuture, PromiseResult, Promises};
246pub use publisher::DXPublisher;
247pub use stream::{EventStream, StreamOptions, TaggedEventStream};
248pub use subscription::{DXFeedSubscription, DXFeedTimeSeriesSubscription, ListenerId};
249pub use subscription_controller::SubscriptionController;
250pub use symbol::Symbol;
251pub use time_format::TimeFormat;
252pub use time_period::{TimePeriod, TimePeriodInfo};
253pub use time_zone::TimeZone;
254
255/// Locks `mutex`, ignoring poisoning: the data it guards stays consistent when a holder panics.
256fn lock<T>(mutex: &std::sync::Mutex<T>) -> std::sync::MutexGuard<'_, T> {
257    mutex.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
258}
259
260/// Returns the value of `cell`, setting it to the result of `init` under `guard` if it is empty. A failed `init` leaves
261/// the cell empty, so that the next call tries again.
262fn get_or_try_init<'a, T, E>(
263    cell: &'a std::sync::OnceLock<T>,
264    guard: &std::sync::Mutex<()>,
265    init: impl FnOnce() -> std::result::Result<T, E>,
266) -> std::result::Result<&'a T, E> {
267    if let Some(value) = cell.get() {
268        return Ok(value);
269    }
270    let _guard = lock(guard);
271    if let Some(value) = cell.get() {
272        return Ok(value);
273    }
274    let value = init()?;
275    Ok(cell.get_or_init(|| value))
276}