-
Notifications
You must be signed in to change notification settings - Fork 538
Signal handling for non-Python WASM modules #6098
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
Draft
logan-gatlin
wants to merge
4
commits into
cloudflare:main
Choose a base branch
from
logan-gatlin:logan/wasm-signal-handling
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+347
−2
Draft
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| // Copyright (c) 2017-2022 Cloudflare, Inc. | ||
| // Licensed under the Apache 2.0 license found in the LICENSE file or at: | ||
| // https://opensource.org/licenses/Apache-2.0 | ||
|
|
||
| #pragma once | ||
|
|
||
| #include <v8-array-buffer.h> | ||
|
|
||
| #include <kj/common.h> | ||
|
|
||
| #include <atomic> | ||
| #include <memory> | ||
|
|
||
| namespace workerd { | ||
|
|
||
| // Byte size of each signal field in WASM linear memory (a single uint32). | ||
| constexpr size_t WASM_SIGNAL_FIELD_BYTES = sizeof(uint32_t); | ||
|
|
||
| // Represents a single WASM module that has opted into receiving the "shut down" signal when CPU | ||
| // time is nearly exhausted. The module exports two i32 globals: | ||
| // | ||
| // "__signal_address" — address of a uint32 in linear memory. The runtime writes 1 here | ||
| // when CPU time is nearly exhausted. | ||
| // "__terminated_address" — address of a uint32 in linear memory. The WASM module writes a | ||
| // non-zero value here when it has exited and is no longer listening. | ||
| // The runtime checks this in a GC prologue hook and removes entries | ||
| // where terminated is non-zero, allowing the linear memory to be | ||
| // reclaimed. | ||
| struct WasmShutdownSignal { | ||
| // This reference is shared rather than weak so that we can be sure it is not being | ||
| // garbage collected when the signal handler runs. This memory gets cleaned up in a | ||
| // V8 GC prelude hook where we can atomically remove it from the signal list before | ||
| // freeing the memory. | ||
| std::shared_ptr<v8::BackingStore> backingStore; | ||
|
|
||
| // Offset into `backingStore` of the uint32 the runtime writes 1 to (__signal_address). | ||
| uint32_t signalByteOffset; | ||
|
|
||
| // Offset into `backingStore` of the uint32 the module writes to (__terminated_address). | ||
| uint32_t terminatedByteOffset; | ||
|
|
||
| // Returns true if the module is still listening for signals (terminated == 0). | ||
| // Returns false if the module has exited and this entry should be removed. | ||
| bool isModuleListening() const { | ||
| uint32_t terminated; | ||
| memcpy(&terminated, static_cast<kj::byte*>(backingStore->Data()) + terminatedByteOffset, | ||
| sizeof(terminated)); | ||
| return terminated == 0; | ||
| } | ||
| }; | ||
|
|
||
| // A linked list type which is signal-safe (for reading), but not thread safe - it can handle | ||
| // same-thread concurrency ONLY. Mutations (pushFront, filter) are not signal safe, but are | ||
| // implemented such that they can be interrupted at any point by a signal handler, and the list will | ||
| // still be in a valid state. This means that reading the list (iterate) IS signal safe. | ||
| template <typename T> | ||
| class AtomicList { | ||
| public: | ||
| struct Node { | ||
| T value; | ||
| Node* next; | ||
| template <typename... Args> | ||
| explicit Node(Args&&... args): value(kj::fwd<Args>(args)...), | ||
| next(nullptr) {} | ||
| }; | ||
|
|
||
| AtomicList() {} | ||
|
|
||
| ~AtomicList() noexcept(false) { | ||
| Node* node = __atomic_load_n(&head, __ATOMIC_RELAXED); | ||
| while (node != nullptr) { | ||
| Node* doomed = node; | ||
| node = __atomic_load_n(&doomed->next, __ATOMIC_RELAXED); | ||
| delete doomed; | ||
| } | ||
| } | ||
|
|
||
| // Prepends a new node constructed from `args` at the front of the list | ||
| template <typename... Args> | ||
| void pushFront(Args&&... args) { | ||
| Node* node = new Node(kj::fwd<Args>(args)...); | ||
| __atomic_store_n(&node->next, __atomic_load_n(&head, __ATOMIC_RELAXED), __ATOMIC_RELAXED); | ||
| __atomic_store_n(&head, node, __ATOMIC_RELEASE); | ||
| } | ||
|
|
||
| // Removes all nodes for which `predicate(node.value)` returns false | ||
| template <typename Predicate> | ||
| void filter(Predicate&& predicate) { | ||
| Node** prev = &head; | ||
| Node* current = __atomic_load_n(prev, __ATOMIC_RELAXED); | ||
|
|
||
| while (current != nullptr) { | ||
| Node* next = __atomic_load_n(¤t->next, __ATOMIC_RELAXED); | ||
|
|
||
| if (predicate(current->value)) { | ||
| prev = ¤t->next; | ||
| } else { | ||
| // Splice out `current` by pointing its predecessor at `next`. Release ordering ensures a | ||
| // signal handler that loads *prev with acquire sees a fully consistent successor chain. | ||
| __atomic_store_n(prev, next, __ATOMIC_RELEASE); | ||
| delete current; | ||
| } | ||
|
|
||
| current = next; | ||
| } | ||
| } | ||
|
|
||
| // Returns true if the list is empty. Signal safe. | ||
| bool isEmpty() const { | ||
| return __atomic_load_n(&head, __ATOMIC_ACQUIRE) == nullptr; | ||
| } | ||
|
|
||
| // Traverses the list, calling `func(node.value)` for each node. Signal safe. | ||
| template <typename Func> | ||
| void iterate(Func&& func) const { | ||
| Node* current = __atomic_load_n(&head, __ATOMIC_ACQUIRE); | ||
| while (current != nullptr) { | ||
| func(current->value); | ||
| current = __atomic_load_n(¤t->next, __ATOMIC_ACQUIRE); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| Node* head = nullptr; | ||
|
|
||
| KJ_DISALLOW_COPY_AND_MOVE(AtomicList); | ||
| }; | ||
|
|
||
| // Iterates a WasmShutdownSignal list and writes the shutdown signal (value 1) to each | ||
| // registered memory location. This function is signal-safe. | ||
| inline void writeWasmShutdownSignals(const AtomicList<WasmShutdownSignal>& signals) { | ||
| signals.iterate([](const WasmShutdownSignal& signal) { | ||
| // Signal-safe: BackingStore::Data() is a trivial getter; memcpy into mapped WASM memory | ||
| // is a plain store. | ||
| uint32_t value = 1; | ||
| memcpy(static_cast<kj::byte*>(signal.backingStore->Data()) + signal.signalByteOffset, &value, | ||
| sizeof(value)); | ||
| }); | ||
| } | ||
|
|
||
| } // namespace workerd | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
how can you ensure that
currentis a valid pointer?while the chain kept intact correctly for reading, i don't think you can ever verify pointer integrity with this method. it would only work with some sort of atomic reference count or something on the nodes, or
am i missing something?