Skip to content

Commit

Permalink
[FabricClient] Support FabricClient user notification callback (#70)
Browse files Browse the repository at this point in the history
Support service notification callback and client connection notification
callbacks.
You can construct FabricClient with callbacks lambda functions like
this:
```rs
let fc = FabricClientBuilder::new()
        .with_on_service_notification(move |notification| {
            Ok(())
        })
        .with_on_client_connect(move |gw| {
            Ok(())
        })
        .with_on_client_disconnect(move |_| {
             Ok(())
        })
        .with_client_role(mssf_core::types::ClientRole::User).build()
```
with_on_service_notification callback is configured by
register_service_notification_filter API.
  • Loading branch information
youyuanwu authored Sep 27, 2024
1 parent d07bb9e commit ed020ab
Show file tree
Hide file tree
Showing 10 changed files with 554 additions and 48 deletions.
138 changes: 138 additions & 0 deletions crates/libs/core/src/client/connection.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// ------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// --------------------------------------------------

use mssf_com::FabricClient::{
IFabricClientConnectionEventHandler, IFabricClientConnectionEventHandler_Impl,
IFabricGatewayInformationResult,
};

use crate::{strings::HSTRINGWrap, types::NodeId};

/// Internal trait that rust code implements that can be bridged into IFabricClientConnectionEventHandler.
/// Not exposed to user.
pub trait ClientConnectionEventHandler: 'static {
fn on_connected(&self, info: &GatewayInformationResult) -> crate::Result<()>;
fn on_disconnected(&self, info: &GatewayInformationResult) -> crate::Result<()>;
}

/// FabricClient connection information.
/// Traslated from IFabricGatewayInformationResult
#[derive(Debug, Clone)]
pub struct GatewayInformationResult {
pub node_address: crate::HSTRING,
pub node_id: NodeId,
pub node_instance_id: u64,
pub node_name: crate::HSTRING,
}

impl GatewayInformationResult {
fn from_com(com: &IFabricGatewayInformationResult) -> Self {
let info = unsafe { com.get_GatewayInformation().as_ref().unwrap() };
Self {
node_address: HSTRINGWrap::from(info.NodeAddress).into(),
node_id: info.NodeId.into(),
node_instance_id: info.NodeInstanceId,
node_name: HSTRINGWrap::from(info.NodeName).into(),
}
}
}

/// Bridge for IFabricClientConnectionEventHandler.
/// Turn rust trait into SF com object.
#[windows_core::implement(IFabricClientConnectionEventHandler)]
pub struct ClientConnectionEventHandlerBridge<T>
where
T: ClientConnectionEventHandler,
{
inner: T,
}

impl<T> ClientConnectionEventHandlerBridge<T>
where
T: ClientConnectionEventHandler,
{
pub fn new(inner: T) -> Self {
Self { inner }
}
pub fn new_com(inner: T) -> IFabricClientConnectionEventHandler {
Self::new(inner).into()
}
}

impl<T> IFabricClientConnectionEventHandler_Impl for ClientConnectionEventHandlerBridge<T>
where
T: ClientConnectionEventHandler,
{
fn OnConnected(
&self,
gw_info: Option<&IFabricGatewayInformationResult>,
) -> windows_core::Result<()> {
let info = GatewayInformationResult::from_com(gw_info.unwrap());
self.inner.on_connected(&info)
}

fn OnDisconnected(
&self,
gw_info: Option<&IFabricGatewayInformationResult>,
) -> windows_core::Result<()> {
let info = GatewayInformationResult::from_com(gw_info.unwrap());
self.inner.on_disconnected(&info)
}
}

/// Connection notification function signature to avoid code repeatition.
/// Trait alias feature in rust (not yet stable) would eliminate this trait definition.
pub trait ConnectionNotificationFn:
Fn(&GatewayInformationResult) -> crate::Result<()> + 'static
{
}
impl<T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static> ConnectionNotificationFn
for T
{
}

/// Lambda implementation of the ClientConnectionEventHandler trait.
/// This is used in FabricClientBuilder to build handler from functions.
pub struct LambdaClientConnectionNotificationHandler {
f_conn: Option<Box<dyn ConnectionNotificationFn>>,
f_disconn: Option<Box<dyn ConnectionNotificationFn>>,
}

impl LambdaClientConnectionNotificationHandler {
pub fn new() -> Self {
Self {
f_conn: None,
f_disconn: None,
}
}

/// Set the on_connected callback.
pub fn set_f_conn(&mut self, f: impl ConnectionNotificationFn) {
self.f_conn = Some(Box::new(f));
}

/// Set the on_disconnected callback.
pub fn set_f_disconn(&mut self, f: impl ConnectionNotificationFn) {
self.f_disconn = Some(Box::new(f));
}
}

impl ClientConnectionEventHandler for LambdaClientConnectionNotificationHandler {
fn on_connected(&self, info: &GatewayInformationResult) -> crate::Result<()> {
if let Some(f) = &self.f_conn {
f(info)
} else {
Ok(())
}
}

fn on_disconnected(&self, info: &GatewayInformationResult) -> crate::Result<()> {
if let Some(f) = &self.f_disconn {
f(info)
} else {
Ok(())
}
}
}
152 changes: 141 additions & 11 deletions crates/libs/core/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,160 @@
// Licensed under the MIT License (MIT). See License.txt in the repo root for license information.
// ------------------------------------------------------------

use connection::{ClientConnectionEventHandlerBridge, LambdaClientConnectionNotificationHandler};
use mssf_com::FabricClient::{
FabricCreateLocalClient4, IFabricClientConnectionEventHandler,
IFabricPropertyManagementClient2, IFabricQueryClient10, IFabricServiceManagementClient6,
IFabricServiceNotificationEventHandler,
};
use notification::{
LambdaServiceNotificationHandler, ServiceNotificationEventHandler,
ServiceNotificationEventHandlerBridge,
};
use windows_core::Interface;

use crate::types::ClientRole;

use self::{query_client::QueryClient, svc_mgmt_client::ServiceManagementClient};

mod connection;
mod notification;
pub mod query_client;
pub mod svc_mgmt_client;

// reexport
pub use connection::GatewayInformationResult;
pub use notification::ServiceNotification;

#[cfg(test)]
mod tests;

/// Creates FabricClient com object using SF com API.
fn create_local_client_internal<T: Interface>(
service_notification_handler: Option<&IFabricServiceNotificationEventHandler>,
client_connection_handler: Option<&IFabricClientConnectionEventHandler>,
client_role: Option<ClientRole>,
) -> T {
let role = client_role.unwrap_or(ClientRole::User);
assert_ne!(
role,
ClientRole::Unknown,
"Unknown role should not be used."
);
let raw = unsafe {
FabricCreateLocalClient4(
service_notification_handler,
client_connection_handler,
role.into(),
&T::IID,
)
}
.expect("failed to create fabric client");
// if params are right, client should be created. There is no network call involved during obj creation.
unsafe { T::from_raw(raw) }
}

// Builder for FabricClient
pub struct FabricClientBuilder {
sn_handler: Option<IFabricServiceNotificationEventHandler>,
cc_handler: Option<LambdaClientConnectionNotificationHandler>,
client_role: ClientRole,
}

impl Default for FabricClientBuilder {
fn default() -> Self {
Self::new()
}
}

impl FabricClientBuilder {
/// Creates the builder.
pub fn new() -> Self {
Self {
sn_handler: None,
cc_handler: None,
client_role: ClientRole::User,
}
}

/// Configures the service notification handler internally.
fn with_service_notification_handler(
mut self,
handler: impl ServiceNotificationEventHandler,
) -> Self {
self.sn_handler = Some(ServiceNotificationEventHandlerBridge::new_com(handler));
self
}

/// Configures the service notification handler.
/// See details in `register_service_notification_filter` API.
/// If the service endpoint change matches the registered filter,
/// this notification is invoked
pub fn with_on_service_notification<T>(self, f: T) -> Self
where
T: Fn(&ServiceNotification) -> crate::Result<()> + 'static,
{
let handler = LambdaServiceNotificationHandler::new(f);
self.with_service_notification_handler(handler)
}

/// When FabricClient connects to the SF cluster, this callback is invoked.
pub fn with_on_client_connect<T>(mut self, f: T) -> Self
where
T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
{
if self.cc_handler.is_none() {
self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
}
if let Some(cc) = self.cc_handler.as_mut() {
cc.set_f_conn(f)
}
self
}

/// When FabricClient disconnets to the SF cluster, this callback is called.
/// This callback is not called on Drop of FabricClient.
pub fn with_on_client_disconnect<T>(mut self, f: T) -> Self
where
T: Fn(&GatewayInformationResult) -> crate::Result<()> + 'static,
{
if self.cc_handler.is_none() {
self.cc_handler = Some(LambdaClientConnectionNotificationHandler::new());
}
if let Some(cc) = self.cc_handler.as_mut() {
cc.set_f_disconn(f)
}
self
}

/// Sets the role of the client connection. Default is User if not set.
pub fn with_client_role(mut self, role: ClientRole) -> Self {
self.client_role = role;
self
}

/// Build the fabricclient
/// Remarks: FabricClient connect to SF cluster when
/// the first API call is triggered. Build/create of the object does not
/// establish connection.
pub fn build(self) -> FabricClient {
let c = Self::build_interface(self);
FabricClient::from_com(c)
}

/// Build the specific com interface of the fabric client.
pub fn build_interface<T: Interface>(self) -> T {
let cc_handler = self
.cc_handler
.map(ClientConnectionEventHandlerBridge::new_com);
create_local_client_internal::<T>(
self.sn_handler.as_ref(),
cc_handler.as_ref(),
Some(self.client_role),
)
}
}

// FabricClient safe wrapper
// The design of FabricClient follows from the csharp client:
// https://github.com/microsoft/service-fabric/blob/master/src/prod/src/managed/Api/src/System/Fabric/FabricClient.cs
Expand All @@ -26,18 +167,7 @@ pub struct FabricClient {
com_query_client: IFabricQueryClient10,
}

impl Default for FabricClient {
fn default() -> Self {
Self::new()
}
}

impl FabricClient {
pub fn new() -> Self {
let com = crate::sync::CreateLocalClient::<IFabricPropertyManagementClient2>();
Self::from_com(com)
}

// Get a copy of COM object
pub fn get_com(&self) -> IFabricPropertyManagementClient2 {
self.com_property_client.clone()
Expand Down
Loading

0 comments on commit ed020ab

Please sign in to comment.