Unverified Commit 3529a6ee authored by Kenton Varda's avatar Kenton Varda Committed by GitHub

Merge pull request #661 from capnproto/userspace-pipe

 Implement in-process byte stream pipes.
parents 394643ba 8ec0cbb5
This diff is collapsed.
This diff is collapsed.
......@@ -175,6 +175,13 @@ struct OneWayPipe {
Own<AsyncOutputStream> out;
};
OneWayPipe newOneWayPipe(kj::Maybe<uint64_t> expectedLength = nullptr);
// Constructs a OneWayPipe that operates in-process. The pipe does not do any buffering -- it waits
// until both a read() and a write() call are pending, then resolves both.
//
// If `expectedLength` is non-null, then the pipe will be expected to transmit exactly that many
// bytes. The input end's `tryGetLength()` will return the number of bytes left.
struct TwoWayPipe {
// A data pipe that supports sending in both directions. Each end's output sends data to the
// other end's input. (Typically backed by socketpair() system call.)
......@@ -182,6 +189,10 @@ struct TwoWayPipe {
Own<AsyncIoStream> ends[2];
};
TwoWayPipe newTwoWayPipe();
// Constructs a TwoWayPipe that operates in-process. The pipe does not do any buffering -- it waits
// until both a read() and a write() call are pending, then resolves both.
struct CapabilityPipe {
// Like TwoWayPipe but allowing capability-passing.
......
......@@ -580,6 +580,29 @@ TEST(Async, ArrayJoinVoid) {
promise.wait(waitScope);
}
TEST(Async, Canceler) {
EventLoop loop;
WaitScope waitScope(loop);
Canceler canceler;
auto never = canceler.wrap(kj::Promise<void>(kj::NEVER_DONE));
auto now = canceler.wrap(kj::Promise<void>(kj::READY_NOW));
auto neverI = canceler.wrap(kj::Promise<void>(kj::NEVER_DONE).then([]() { return 123u; }));
auto nowI = canceler.wrap(kj::Promise<uint>(123u));
KJ_EXPECT(!never.poll(waitScope));
KJ_EXPECT(now.poll(waitScope));
KJ_EXPECT(!neverI.poll(waitScope));
KJ_EXPECT(nowI.poll(waitScope));
canceler.cancel("foobar");
KJ_EXPECT_THROW_MESSAGE("foobar", never.wait(waitScope));
now.wait(waitScope);
KJ_EXPECT_THROW_MESSAGE("foobar", neverI.wait(waitScope));
KJ_EXPECT(nowI.wait(waitScope) == 123u);
}
class ErrorHandlerImpl: public TaskSet::ErrorHandler {
public:
uint exceptionCount = 0;
......
......@@ -84,6 +84,76 @@ public:
} // namespace
// =======================================================================================
Canceler::~Canceler() noexcept(false) {
cancel("operation canceled");
}
void Canceler::cancel(StringPtr cancelReason) {
if (isEmpty()) return;
cancel(Exception(Exception::Type::FAILED, __FILE__, __LINE__, kj::str(cancelReason)));
}
void Canceler::cancel(const Exception& exception) {
for (;;) {
KJ_IF_MAYBE(a, list) {
list = a->next;
a->prev = nullptr;
a->next = nullptr;
a->cancel(kj::cp(exception));
} else {
break;
}
}
}
void Canceler::release() {
for (;;) {
KJ_IF_MAYBE(a, list) {
list = a->next;
a->prev = nullptr;
a->next = nullptr;
} else {
break;
}
}
}
Canceler::AdapterBase::AdapterBase(Canceler& canceler)
: prev(canceler.list),
next(canceler.list) {
canceler.list = *this;
KJ_IF_MAYBE(n, next) {
n->prev = next;
}
}
Canceler::AdapterBase::~AdapterBase() noexcept(false) {
KJ_IF_MAYBE(p, prev) {
*p = next;
}
KJ_IF_MAYBE(n, next) {
n->prev = prev;
}
}
Canceler::AdapterImpl<void>::AdapterImpl(kj::PromiseFulfiller<void>& fulfiller,
Canceler& canceler, kj::Promise<void> inner)
: AdapterBase(canceler),
fulfiller(fulfiller),
inner(inner.then(
[&fulfiller]() { fulfiller.fulfill(); },
[&fulfiller](kj::Exception&& e) { fulfiller.reject(kj::mv(e)); })
.eagerlyEvaluate(nullptr)) {}
void Canceler::AdapterImpl<void>::cancel(kj::Exception&& e) {
fulfiller.reject(kj::mv(e));
inner = nullptr;
}
// =======================================================================================
TaskSet::TaskSet(TaskSet::ErrorHandler& errorHandler)
: errorHandler(errorHandler) {}
......
......@@ -500,6 +500,109 @@ PromiseFulfillerPair<T> newPromiseAndFulfiller();
// fulfiller will be of type `PromiseFulfiller<Promise<U>>`. Thus you pass a `Promise<U>` to the
// `fulfill()` callback, and the promises are chained.
// =======================================================================================
// Canceler
class Canceler {
// A Canceler can wrap some set of Promises and then forcefully cancel them on-demand, or
// implicitly when the Canceler is destroyed.
//
// The cancellation is done in such a way that once cancel() (or the Canceler's destructor)
// returns, it's guaranteed that the promise has already been canceled and destroyed. This
// guarantee is important for enforcing ownership constraints. For example, imagine that Alice
// calls a method on Bob that returns a Promise. That Promise encapsulates a task that uses Bob's
// internal state. But, imagine that Alice does not own Bob, and indeed Bob might be destroyed
// at random without Alice having canceled the promise. In this case, it is necessary for Bob to
// ensure that the promise will be forcefully canceled. Bob can do this by constructing a
// Canceler and using it to wrap promises before returning them to callers. When Bob is
// destroyed, the Canceler is destroyed too, and all promises Bob wrapped with it throw errors.
//
// Note that another common strategy for cancelation is to use exclusiveJoin() to join a promise
// with some "cancellation promise" which only resolves if the operation should be canceled. The
// cancellation promise could itself be created by newPromiseAndFulfiller<void>(), and thus
// calling the PromiseFulfiller cancels the operation. There is a major problem with this
// approach: upon invoking the fulfiller, an arbitrary amount of time may pass before the
// exclusive-joined promise actually resolves and cancels its other fork. During that time, the
// task might continue to execute. If it holds pointers to objects that have been destroyed, this
// might cause segfaults. Thus, it is safer to use a Canceler.
public:
inline Canceler() {}
~Canceler() noexcept(false);
KJ_DISALLOW_COPY(Canceler);
template <typename T>
Promise<T> wrap(Promise<T> promise) {
return newAdaptedPromise<T, AdapterImpl<T>>(*this, kj::mv(promise));
}
void cancel(StringPtr cancelReason);
void cancel(const Exception& exception);
// Cancel all previously-wrapped promises that have not already completed, causing them to throw
// the given exception. If you provide just a description message instead of an exception, then
// an exception object will be constructed from it -- but only if there are requests to cancel.
void release();
// Releases previously-wrapped promises, so that they will not be canceled regardless of what
// happens to this Canceler.
bool isEmpty() { return list == nullptr; }
// Indicates if any previously-wrapped promises are still executing. (If this returns false, then
// cancel() would be a no-op.)
private:
class AdapterBase {
public:
AdapterBase(Canceler& canceler);
~AdapterBase() noexcept(false);
virtual void cancel(Exception&& e) = 0;
private:
Maybe<Maybe<AdapterBase&>&> prev;
Maybe<AdapterBase&> next;
friend class Canceler;
};
template <typename T>
class AdapterImpl: public AdapterBase {
public:
AdapterImpl(PromiseFulfiller<T>& fulfiller,
Canceler& canceler, Promise<T> inner)
: AdapterBase(canceler),
fulfiller(fulfiller),
inner(inner.then(
[&fulfiller](T&& value) { fulfiller.fulfill(kj::mv(value)); },
[&fulfiller](Exception&& e) { fulfiller.reject(kj::mv(e)); })
.eagerlyEvaluate(nullptr)) {}
void cancel(Exception&& e) override {
fulfiller.reject(kj::mv(e));
inner = nullptr;
}
private:
PromiseFulfiller<T>& fulfiller;
Promise<void> inner;
};
Maybe<AdapterBase&> list;
};
template <>
class Canceler::AdapterImpl<void>: public AdapterBase {
public:
AdapterImpl(kj::PromiseFulfiller<void>& fulfiller,
Canceler& canceler, kj::Promise<void> inner);
void cancel(kj::Exception&& e) override;
// These must be defined in async.c++ to prevent translation units compiled by MSVC from trying to
// link with symbols defined in async.c++ merely because they included async.h.
private:
kj::PromiseFulfiller<void>& fulfiller;
kj::Promise<void> inner;
};
// =======================================================================================
// TaskSet
......
This diff is collapsed.
......@@ -2492,8 +2492,10 @@ public:
bodyStream = heap<HttpChunkedEntityWriter>(httpOutput);
}
auto responsePromise = httpInput.readResponseHeaders()
.then([this,method](kj::Maybe<HttpHeaders::Response>&& response) -> HttpClient::Response {
auto id = ++counter;
auto responsePromise = httpInput.readResponseHeaders().then(
[this,method,id](kj::Maybe<HttpHeaders::Response>&& response) -> HttpClient::Response {
KJ_IF_MAYBE(r, response) {
auto& headers = httpInput.getHeaders();
HttpClient::Response result {
......@@ -2506,8 +2508,11 @@ public:
if (fastCaseCmp<'c', 'l', 'o', 's', 'e'>(
headers.get(HttpHeaderId::CONNECTION).orDefault(nullptr).cStr())) {
closed = true;
} else {
} else if (counter == id) {
watchForClose();
} else {
// Anothe request was already queued after this one, so we don't want to watch for
// stream closure because we're fully expecting another response.
}
return result;
} else {
......@@ -2550,9 +2555,11 @@ public:
// No entity-body.
httpOutput.finishBody();
auto id = ++counter;
return httpInput.readResponseHeaders()
.then(kj::mvCapture(keyBase64,
[this](kj::StringPtr keyBase64, kj::Maybe<HttpHeaders::Response>&& response)
[this,id](kj::StringPtr keyBase64, kj::Maybe<HttpHeaders::Response>&& response)
-> HttpClient::WebSocketResponse {
KJ_IF_MAYBE(r, response) {
auto& headers = httpInput.getHeaders();
......@@ -2593,8 +2600,11 @@ public:
if (fastCaseCmp<'c', 'l', 'o', 's', 'e'>(
headers.get(HttpHeaderId::CONNECTION).orDefault(nullptr).cStr())) {
closed = true;
} else {
} else if (counter == id) {
watchForClose();
} else {
// Anothe request was already queued after this one, so we don't want to watch for
// stream closure because we're fully expecting another response.
}
return result;
}
......@@ -2614,6 +2624,10 @@ private:
bool upgraded = false;
bool closed = false;
uint counter = 0;
// Counts requests for the sole purpose of detecting if more requests have been made after some
// point in history.
void watchForClose() {
closeWatcherTask = httpInput.awaitNextMessage().then([this](bool hasData) {
if (hasData) {
......
......@@ -131,7 +131,7 @@ public:
inline bool isShared() const {
#if _MSC_VER
KJ_MSVC_INTERLOCKED(Or, acq)(&refcount, 0) > 1;
return KJ_MSVC_INTERLOCKED(Or, acq)(&refcount, 0) > 1;
#else
return __atomic_load_n(&refcount, __ATOMIC_ACQUIRE) > 1;
#endif
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment