Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to use callback functions when calling celix service #775

Open
xuzhenbao opened this issue Oct 16, 2024 · 1 comment
Open

How to use callback functions when calling celix service #775

xuzhenbao opened this issue Oct 16, 2024 · 1 comment

Comments

@xuzhenbao
Copy link
Contributor

In some service usage scenarios, the result may not be returned immediately after the service call is completed, it may be returned through a callback function at some time after the service call is completed. How should we use callback functions in celix services (it includes remote services and local services)?

In my opinion, it is unsafe to design function pointers directly in the service method parameters, because the service provider may have been destroyed after the service call is completed. At this time, if the callback function is called again, problems will occur. In addition, designing callback function pointers in the service method may make the implementation of RSA complex.

Therefore, I think the callback function can be designed as a celix service. If a service method needs a callback function, the service method's input parameters should include the service identifier of the callback function (Maybe the service identifier is a string composed of framework uuid and service id). When calling the service method, the service caller needs to register the callback function service first, and then pass the callback function service identifier to the service method. The service provider can invoke the callback function through the callback function service. The example is as follows:

//provider api

#define MY_SERVICE_NAME "my_service"
#define MY_SERVICE_VERSION "1.0.0"

typedef struct my_service {
    void* handle;
    void (*doSomething)(void *handle, const char *input, const char *callbackServiceUUID);
} my_service_t;

#define MY_SERVICE_CALLBACK_SERVICE_NAME "my_service_callback"
#define MY_SERVICE_CALLBACK_SERVICE_VERSION "1.0.0"
typedef struct callback_service {
    void* handle;
    void (*callback)(void *handle, const char *result);
} callback_service_t;
//provider impl
typedef struct provider {
    celix_bundle_context_t *context;
    my_service_t service;
    long svcId;
    long trkId;
} provider_t;

typedef struct use_callback_data {
    provider_t *provider;
    const char *input;
    const char *callbackServiceUUID;
}use_callback_data_t;

void useCallback(void *handle, void *svc) {
    use_callback_data_t *data = handle;
    callback_service_t *callback = svc;
    callback->callback(callback->handle, "result");
}

void my_service_doSomething(void *handle, const char *input, const char *callbackServiceUUID) {
    provider_t *provider = handle;

// invoke callback service
    use_callback_data_t data = {provider, input, callbackServiceUUID};
    celix_bundleContext_useTrackedServices(provider->context, provider->trkId, &data, useCallback);
}

void provider_init(celix_bundle_context_t *context, provider_t *provider) {
    provider->context = context;
    provider->service.handle = provider;
    provider->service.doSomething = my_service_doSomething;
    provider->svcId = celix_bundleContext_registerService(context, &provider->service, MY_SERVICE_NAME, MY_SERVICE_VERSION, NULL);

//track callback service
    celix_service_tracking_options_t opts = CELIX_EMPTY_SERVICE_TRACKING_OPTIONS;
    opts.filter.serviceName = MY_SERVICE_CALLBACK_SERVICE_NAME;
    provider->trkId = celix_bundleContext_trackServicesWithOptions(ctx, &opts);
}
//consumer impl

typedef struct consumer {
    celix_bundle_context_t *context;
    callback_service_t service;
    long svcId;
    long trkId;
} consumer_t;

void my_service_callback(void *handle, const char *result) {
    printf("my_service_callback called with result %s\n", result);
}

void useService(void *handle, void *svc) {
    consumer_t *consumer = handle;
    my_service_t *service = svc;
    service->doSomething(service->handle, "input", "<frameworkUUID>+<service id>");
}

void consumer_init(celix_bundle_context_t *context, consumer_t *consumer) {
// register callback function service
    consumer->context = context;
    consumer->service.handle = consumer;
    consumer->service.callback = my_service_callback;
    consumer->svcId = celix_bundleContext_registerService(context, &consumer->service, MY_SERVICE_CALLBACK_SERVICE_NAME, MY_SERVICE_CALLBACK_SERVICE_VERSION, NULL);

//use target service
    celix_service_use_options_t opts = CELIX_EMPTY_SERVICE_USE_OPTIONS;
    opts.filter.serviceName = MY_SERVICE_NAME;
    celix_bundleContext_useServiceWithOptions(context, &opts, consumer, useService);
}

@pnoltes
Do you have any suggestions for the design of callback function?

@pnoltes
Copy link
Contributor

pnoltes commented Oct 22, 2024

Interesting question.

Yes in many cases I think this can be solved as you mention, but this does mean that the service are heavily intertwined.
From an abstraction point of view this is not very nice (not a "cohesive" interface).

IMO the C language is missing some async concepts to do this nicely. For C++ std::future could be used and the OSGi spec has also something for this OSGi Promise. ASF Celix also has a implementation for Promises, but only for C++.

Let assume there was also C implementation for this. The instead of a callback the provide api could look something like:

//provider api
#include <celix_promise.h>

#define MY_SERVICE_NAME "my_service"
#define MY_SERVICE_VERSION "1.0.0"

typedef struct my_service {
    void* handle;
    celix_promise_t (*doSomething)(void *handle, const char *input, const char *callbackServiceUUID);

Maybe with promise api like:

//celix_promise_factory.h

celix_deferred_t celix_promiseFactory_deferred(celix_promise_factory_t* fac);

//celix_promise.h

celix_promise_onSuccess(celix_promise_t promise, void* data, void (*done)(void* data, void* result));

//celix_deferred.h
celix_promise_t celix_deferred_getPromise(celix_deferred_t deferred);
void celix_deferred_resolvePromise(celix_deferred_t deferred, void* result);
void celix_deferred_fail(celix_deferred_t deferred, int error, const char* errorMsg);

And a separate promise api for a long, double, boolean, void, and array_list` promise.

The provider could do something like:

celix_promise_t my_service_doSomething(void *handle, const char *input, const char *callbackServiceUUID) {
    provider_t *provider = handle;

    celix_deferred_t def = celix_promiseFactory_deferred(provider->factory);
    provider->queueAsyncProcessing(def);
    return celix_deferred_getPromise(def);
}

And the consumer snippet:

void useServiceCallback(void* data, void* result) {
   consumer_t *consumer = handle;
   //handle real result.
}

void useService(void *handle, void *svc) {
   consumer_t *consumer = handle;
   celix_promise_t promise =  service->doSomething(service->handle, "input", "<frameworkUUID>+<service id>");
   celix_arrayList_add(consumer->activePromises, promise);
   celix_promise_onSuccess(promise, consumer, useServiceCallback);
}

This also means that the provider is not aware how the consumer will handle the (async) result.

Important concept would be that both the provided and consumer can drop the promise/deferred and it should clean itself up.

But maybe something already exists for this. Because although I like the Promise concept, I am not sure if we need a C implementation for this.

Also this concept then needs to be directly supported in remote services (first class citizen) so that the remote service admins can generated endpoint/proxies that can handle promise and deferred objects. For Java this is also mentioned in the remote service admin spec: https://docs.osgi.org/specification/osgi.cmpn/8.1.0/service.remoteservices.html#d0e1444

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants