Skip to content

Commit 0f682c8

Browse files
committed
Run some services concurrently
This implements a general way to run some of our services concurrently, notably _just_ the ones that are supposed to be run on one node manager specifically, like `Read` and `Write`. This involved some slightly fiddly async code, but the result isn't too bad, and allows us to generalize a few things that we're not very consistent about, like setting `current_node_manager_index` between calls. Doing this properly required cloning the `RequestContext`. I'm concerned about the cost of this clone, which up until now we've been pretty lazy about, so I moved the shared part into an `inner` field behind an `Arc`. To avoid this breaking every server implementation I added a `Deref<RequestContextInner>` to `RequestContext` which is a bad practice, but does seem alright in this case.
1 parent db54ce3 commit 0f682c8

11 files changed

Lines changed: 676 additions & 336 deletions

File tree

TODO.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,4 @@ This is a list of things that are known to be missing, or ideas that could be im
1111
- Write some "bad ideas" servers, it would be nice to showcase how flexible this is.
1212
- Write a framework for method calls. The foundation for this has been laid with `TryFromVariant`, if we really wanted to we could use clever trait magic to let users simply define a rust method that takes in values that each implement a trait `MethodArg`, with a blanket impl for `TryFromVariant`, and return a tuple of results. Could be really powerful, but methods are a little niche.
1313
- Implement `Query`. I never got around to this, because the service is just so complex. Currently there is no way to actually implement it, since it won't work unless _all_ node managers implement it, and the core node managers don't.
14-
- Look into running certain services concurrently. Currently they are sequential because that makes everything much simpler, but the services that don't have any cross node-manager interaction could run on all node managers concurrently.
1514
- Tracing and detailed logging in the client.

async-opcua-server/src/node_manager/context.rs

Lines changed: 56 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::sync::Arc;
1+
use std::{ops::Deref, sync::Arc};
22

33
use crate::{
44
authenticator::{AuthManager, UserToken},
@@ -83,9 +83,27 @@ impl<R: RawRwLock, T: TypeTree> TypeTreeReadContext for RwLockReadGuard<'_, R, T
8383
}
8484

8585
#[derive(Clone)]
86-
/// Context object passed during writes, contains useful context the node
86+
/// Context object passed during requests, contains useful context the node
8787
/// managers can use to execute service calls.
8888
pub struct RequestContext {
89+
/// Index of the current node manager.
90+
pub current_node_manager_index: usize,
91+
/// Inner request context object, shared between service calls.
92+
pub(crate) inner: Arc<RequestContextInner>,
93+
}
94+
95+
// This isn't ideal, but the breaking change from having every field on
96+
// RequestContext be private is too big for now.
97+
impl Deref for RequestContext {
98+
type Target = RequestContextInner;
99+
100+
fn deref(&self) -> &RequestContextInner {
101+
&self.inner
102+
}
103+
}
104+
105+
/// Inner request context object, shared between service calls.
106+
pub struct RequestContextInner {
89107
/// The full session object for the session responsible for this service call.
90108
pub session: Arc<RwLock<Session>>,
91109
/// The session ID for the session responsible for this service call.
@@ -94,8 +112,6 @@ pub struct RequestContext {
94112
pub authenticator: Arc<dyn AuthManager>,
95113
/// The current user token.
96114
pub token: UserToken,
97-
/// Index of the current node manager.
98-
pub current_node_manager_index: usize,
99115
/// Global type tree object.
100116
pub type_tree: Arc<RwLock<DefaultTypeTree>>,
101117
/// Wrapper to get a type tree
@@ -112,6 +128,42 @@ impl RequestContext {
112128
pub fn get_type_tree_for_user<'a>(&'a self) -> Box<dyn TypeTreeReadContext + 'a> {
113129
self.type_tree_getter.get_type_tree_for_user(self)
114130
}
131+
132+
/// Get the session object responsible for this service call.
133+
pub fn session(&self) -> &RwLock<Session> {
134+
&self.session
135+
}
136+
137+
/// Get the session ID for the session responsible for this service call.
138+
pub fn session_id(&self) -> u32 {
139+
self.session_id
140+
}
141+
142+
/// Get the global `AuthManager` object.
143+
pub fn authenticator(&self) -> &dyn AuthManager {
144+
self.authenticator.as_ref()
145+
}
146+
147+
/// Get the current user token.
148+
pub fn user_token(&self) -> &UserToken {
149+
&self.token
150+
}
151+
152+
/// Get the global type tree object. If your server needs per-user type trees,
153+
/// use `get_type_tree_for_user` instead.
154+
pub fn type_tree(&self) -> &RwLock<DefaultTypeTree> {
155+
&self.type_tree
156+
}
157+
158+
/// Get the subscription cache.
159+
pub fn subscriptions(&self) -> &SubscriptionCache {
160+
&self.subscriptions
161+
}
162+
163+
/// Get the server info object.
164+
pub fn info(&self) -> &ServerInfo {
165+
&self.info
166+
}
115167
}
116168

117169
/// Resolve a list of references.

async-opcua-server/src/node_manager/history.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ use opcua_types::{
44
match_extension_object_owned, ByteString, DeleteAtTimeDetails, DeleteEventDetails,
55
DeleteRawModifiedDetails, DynEncodable, ExtensionObject, HistoryData, HistoryEvent,
66
HistoryModifiedData, HistoryReadResult, HistoryReadValueId, HistoryUpdateResult, NodeId,
7-
NumericRange, QualifiedName, ReadAnnotationDataDetails, ReadAtTimeDetails, ReadEventDetails,
8-
ReadProcessedDetails, ReadRawModifiedDetails, StatusCode, UpdateDataDetails,
7+
NumericRange, ObjectId, QualifiedName, ReadAnnotationDataDetails, ReadAtTimeDetails,
8+
ReadEventDetails, ReadProcessedDetails, ReadRawModifiedDetails, StatusCode, UpdateDataDetails,
99
UpdateEventDetails, UpdateStructureDataDetails,
1010
};
1111

@@ -220,7 +220,13 @@ impl HistoryUpdateNode {
220220
self.operation_results = operation_results;
221221
}
222222

223-
pub(crate) fn into_result(self) -> HistoryUpdateResult {
223+
pub(crate) fn into_result(mut self) -> HistoryUpdateResult {
224+
// Special case reads on the server node, to return a proper error if no node manager supports it...
225+
if self.details.node_id() == &ObjectId::Server
226+
&& matches!(self.status, StatusCode::BadNodeIdUnknown)
227+
{
228+
self.status = StatusCode::BadHistoryOperationUnsupported;
229+
}
224230
HistoryUpdateResult {
225231
diagnostic_infos: None,
226232
status_code: self.status,

async-opcua-server/src/node_manager/mod.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,10 @@ use super::{
4040
pub use {
4141
attributes::{ParsedReadValueId, ParsedWriteValue, ReadNode, WriteNode},
4242
build::NodeManagerBuilder,
43-
context::{RequestContext, TypeTreeForUser, TypeTreeForUserStatic, TypeTreeReadContext},
43+
context::{
44+
RequestContext, RequestContextInner, TypeTreeForUser, TypeTreeForUserStatic,
45+
TypeTreeReadContext,
46+
},
4447
history::{HistoryNode, HistoryResult, HistoryUpdateDetails, HistoryUpdateNode},
4548
method::MethodCall,
4649
monitored_items::{MonitoredItemRef, MonitoredItemUpdateRef},

async-opcua-server/src/session/message_handler.rs

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use tracing::{debug, warn};
99
use crate::{
1010
authenticator::UserToken,
1111
info::ServerInfo,
12-
node_manager::{get_namespaces_for_user, NodeManagers, RequestContext},
12+
node_manager::{get_namespaces_for_user, NodeManagers, RequestContext, RequestContextInner},
1313
session::services,
1414
subscriptions::{PendingPublish, SubscriptionCache},
1515
};
@@ -121,15 +121,17 @@ impl<T> Request<T> {
121121
/// Get a request context object from this request.
122122
pub(super) fn context(&self) -> RequestContext {
123123
RequestContext {
124-
session: self.session.clone(),
125-
authenticator: self.info.authenticator.clone(),
126-
token: self.token.clone(),
127124
current_node_manager_index: 0,
128-
type_tree: self.info.type_tree.clone(),
129-
type_tree_getter: self.info.type_tree_getter.clone(),
130-
subscriptions: self.subscriptions.clone(),
131-
session_id: self.session_id,
132-
info: self.info.clone(),
125+
inner: Arc::new(RequestContextInner {
126+
session: self.session.clone(),
127+
authenticator: self.info.authenticator.clone(),
128+
token: self.token.clone(),
129+
type_tree: self.info.type_tree.clone(),
130+
type_tree_getter: self.info.type_tree_getter.clone(),
131+
subscriptions: self.subscriptions.clone(),
132+
session_id: self.session_id,
133+
info: self.info.clone(),
134+
}),
133135
}
134136
}
135137
}
@@ -366,15 +368,17 @@ impl MessageHandler {
366368
}
367369

368370
let mut context = RequestContext {
369-
session,
370-
session_id,
371-
authenticator: self.info.authenticator.clone(),
372-
token,
373371
current_node_manager_index: 0,
374-
type_tree: self.info.type_tree.clone(),
375-
subscriptions: self.subscriptions.clone(),
376-
info: self.info.clone(),
377-
type_tree_getter: self.info.type_tree_getter.clone(),
372+
inner: Arc::new(RequestContextInner {
373+
session,
374+
session_id,
375+
authenticator: self.info.authenticator.clone(),
376+
token,
377+
type_tree: self.info.type_tree.clone(),
378+
subscriptions: self.subscriptions.clone(),
379+
info: self.info.clone(),
380+
type_tree_getter: self.info.type_tree_getter.clone(),
381+
}),
378382
};
379383

380384
// Ignore the result
@@ -397,15 +401,17 @@ impl MessageHandler {
397401
token: UserToken,
398402
) -> NamespaceMap {
399403
let ctx = RequestContext {
400-
session,
401-
authenticator: self.info.authenticator.clone(),
402-
token,
403404
current_node_manager_index: 0,
404-
type_tree: self.info.type_tree.clone(),
405-
type_tree_getter: self.info.type_tree_getter.clone(),
406-
subscriptions: self.subscriptions.clone(),
407-
session_id,
408-
info: self.info.clone(),
405+
inner: Arc::new(RequestContextInner {
406+
session,
407+
session_id,
408+
authenticator: self.info.authenticator.clone(),
409+
token,
410+
type_tree: self.info.type_tree.clone(),
411+
subscriptions: self.subscriptions.clone(),
412+
info: self.info.clone(),
413+
type_tree_getter: self.info.type_tree_getter.clone(),
414+
}),
409415
};
410416
get_namespaces_for_user(&ctx, &self.node_managers)
411417
}

0 commit comments

Comments
 (0)