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

Tracking ACKs for published events; Futures API #1144

Merged
merged 22 commits into from
Nov 22, 2016

Conversation

sergeuz
Copy link
Member

@sergeuz sergeuz commented Oct 22, 2016

This PR implements a generic approach for handling results of asynchronous operations in user space (via Futures), and also implements tracking of acknowledgements for published events, so that user can have a guarantee that published event was actually received by the server. Beginning of the discussion can be found here: #1034.

Futures API

Future is a handle for an asynchronous operation. It's possible to poll future's state, wait on future synchronously, or register a callback that will be invoked when operation completes (either successfully or with an error).

For example:

// Polling
auto f1 = Particle.publish("test"); // Returns Future<void>
while (!f1.isDone()) {
    // ...
}
if (f1.isSucceded()) {
    // ...
}

// Synchronous waiting
auto f2 = Particle.publish("test");
f2.wait(); // Runs event loop until operation completes
if (f2.isFailed()) {
    Log.error("Unable to publish event: %s", f2.error().message());
}

// Registering completion callbacks
Particle.publish("test")
        .onSuccess([]() {
            // ...
        })
        .onError([](Error error) {
            Log.error("Unable to publish event: %s", error.message());
        });
// or
Particle.publish("test")
        .onSuccess(eventSentFunc)
        .onError(eventErrorFunc);

Internally, futures are created via Promise objects, which in turn are created separately for each operation that needs to be processed asynchronously (if we want to expose result of such operation to user). One can think of the promises and futures as write-only and read-only interfaces for accessing result of an operation. Trivial example:

Promise<int> p;
Future<int> f = p.future();
p.setResult(1);
Log.info("Result: %d", f.result()); // Prints "Result: 1"

Promise can be passed to a system function in the following way:

Promise<int> p;
completion_callback callback = p.systemCallback;
void* data = p.dataPtr();
some_system_function(callback, data); // C function

completion_callback is a generic callback type for completion handling within the system (declared here). Promise provides built-in implementation of this callback, which can be used for simple result types that don't require ABI compatibility checks.

There's also convenience wrapper class named CompletionHandler, which acts as a smart pointer for raw completion callbacks and provides a guarantee that a callback will be always invoked with some result or error:

void InternalImpl::do_something(CompletionHandler handler) {
    // Next line is commented out, so completion callback will be invoked with an error by CompletionHandler's destructor
    // handler.setResult(1);
}

void some_system_function(completion_callback callback, void* data) {
    CompletionHandler handler(callback, data);
    internal_impl->do_something(std::move(handler)); // CompletionHandler instances cannot be copied
}

Publishing events with confirmation

Particle.publish() was updated to return Future<void>. Additionally, WITH_ACK flag was added to instruct the protocol implementation that caller wants to confirm receiving of an event at the server side. If Particle.publish() is invoked without WITH_ACK flag, returned future will be set to completed state after sending event message to the server and without waiting for corresponding ACK message.

Test application:

#include "application.h"

SerialLogHandler logHandler(LOG_LEVEL_WARN, {
    { "app", LOG_LEVEL_ALL },
    { "comm.coap", LOG_LEVEL_ALL }
});

const unsigned startupDelay = 3000;

uint32_t t = 0;
bool done = false;

void eventSent() {
    Log.info("Event: OK");
}

void eventError(const Error& error) {
    Log.error("Event: %s", error.message());
}

void setup() {
    t = millis();
}

void loop() {
    if (done || millis() - t < startupDelay) {
        return;
    }

    Log.info("Sending event (NO_ACK)");
    Particle.publish("test", NO_ACK)
            .onSuccess(eventSent)
            .onError(eventError)
            .wait(); // Send synchronously

    Log.info("Sending event (WITH_ACK)");
    Particle.publish("test", WITH_ACK)
            .onSuccess(eventSent)
            .onError(eventError)
            .wait();

    Log.info("Sending event (default flags)");
    Particle.publish("test")
            .onSuccess(eventSent)
            .onError(eventError)
            .wait();

    done = true;
}

Possible logging output (debug build):

0000015539 [app] INFO: Sending event (NO_ACK)
0000015750 [app] INFO: Event: OK
0000015750 [app] INFO: Sending event (WITH_ACK)
0000016065 [comm.coap] TRACE: recieved ACK for message 7
0000016076 [app] INFO: Event: OK
0000016082 [app] INFO: Sending event (default flags)
0000016302 [app] INFO: Event: OK
0000016393 [comm.coap] TRACE: recieved ACK for message 8

On both Photon and Electron, sending of the second event should finish only after receiving ACK for corresponding event message.

Note that this branch is based on feature/usb_logging_1_of_2 branch that introduces Vector container.


Doneness:

  • Contributor has signed CLA
  • Problem and Solution clearly stated
  • Code peer reviewed
  • API tests compiled
  • Run unit/integration/application tests on device
  • Add documentation
  • Add to CHANGELOG.md after merging (add links to docs and issues)

Features

  • Added tracking of ACKs for published events (see WITH_ACK flag for Particle.publish()) [PR #1144]

@sergeuz sergeuz added this to the 0.7.x milestone Oct 22, 2016
@technobly technobly mentioned this pull request Nov 21, 2016
7 tasks
@technobly technobly merged commit 4ee7d3c into feature/usb_logging_1_of_2 Nov 22, 2016
@technobly technobly deleted the feature/publish_ack branch November 22, 2016 06:46
@technobly technobly removed this from the 0.7.x milestone Nov 29, 2016
@technobly technobly modified the milestones: 0.6.1, 0.7.x Nov 29, 2016
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

Successfully merging this pull request may close these issues.

3 participants