Genode: add scheduleCallbacks to asyncdispatch (#20708)

* Genode: add native signal handler

* Genode: add scheduleCallbacks to asyncdispatch

This resolves some awkwardness where an RPC server may or may not
use callSoon while dispatching RPC but without scheduling timers
or I/O.
This commit is contained in:
ehmry
2022-10-31 13:24:57 -05:00
committed by GitHub
parent 39f925b95d
commit 0b262e9496
5 changed files with 188 additions and 3 deletions

View File

@@ -0,0 +1,21 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2022 Emery Hemingway
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
type Constructible*[T] {.
importcpp: "Genode::Constructible",
header: "<util/reconstructible.h>", byref, pure.} = object
proc construct*[T](x: Constructible[T]) {.importcpp.}
## Construct a constructible C++ object.
proc destruct*[T](x: Constructible[T]) {.importcpp.}
## Destruct a constructible C++ object.
proc constructed*[T](x: Constructible[T]): bool {.importcpp.}
## Test if an object is constructed.

View File

@@ -0,0 +1,22 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2022 Emery Hemingway
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## See `Genode Foundations - Entrypoint <https://genode.org/documentation/genode-foundations/21.05/functional_specification/Entrypoint.html>`
## for a description of Entrypoints.
type
EntrypointObj {.
importcpp: "Genode::Entrypoint",
header: "<base/entrypoint.h>",
pure.} = object
Entrypoint* = ptr EntrypointObj
## Opaque Entrypoint object.
proc ep*(env: GenodeEnv): Entrypoint {.importcpp: "(&#->ep())".}
## Access the entrypoint associated with `env`.

77
lib/genode/signals.nim Normal file
View File

@@ -0,0 +1,77 @@
#
#
# Nim's Runtime Library
# (c) Copyright 2022 Emery Hemingway
#
# See the file "copying.txt", included in this
# distribution, for details about the copyright.
#
## See `Genode Foundations - Asynchronous notifications <https://genode.org/documentation/genode-foundations/21.05/architecture/Inter-component_communication.html#Asynchronous_notifications>`
## for a description of Genode signals.
when not defined(genode) or defined(nimdoc):
{.error: "Genode only module".}
import ./entrypoints, ./constructibles
export ep # Entrypoint accessor on GenodeEnv
type
SignalContextCapability* {.
importcpp: "Genode::Signal_context_capability",
header: "<base/signal.h>", pure.} = object
## Capability to an asynchronous signal context.
proc isValid*(cap: SignalContextCapability): bool {.importcpp: "#.valid()".}
## Call the Genode core to check if this `SignalContextCapability` is valid.
# TODO: RpcEffect
type
HandlerProc = proc () {.closure, gcsafe.}
SignalHandlerBase {.
importcpp: "Nim::SignalHandler",
header: "genode_cpp/signals.h",
pure.} = object
SignalHandlerCpp = Constructible[SignalHandlerBase]
SignalHandlerObj = object
cpp: SignalHandlerCpp
cb: HandlerProc
## Signal handling procedure called during dispatch.
SignalHandler* = ref SignalHandlerObj
## Nim object enclosing a Genode signal handler.
proc construct(cpp: SignalHandlerCpp; ep: Entrypoint; sh: SignalHandler) {.importcpp.}
proc cap(cpp: SignalHandlerCpp): SignalContextCapability {.importcpp: "#->cap()".}
proc newSignalHandler*(ep: Entrypoint; cb: HandlerProc): SignalHandler =
## Create a new signal handler. A label is recommended for
## debugging purposes. A signal handler will not be garbage
## collected until after it has been dissolved.
result = SignalHandler(cb: cb)
result.cpp.construct(ep, result)
GCref result
proc dissolve*(sig: SignalHandler) =
## Dissolve signal dispatcher from entrypoint.
# TODO: =destroy?
destruct sig.cpp
sig.cb = nil # lose the callback
GCunref sig
proc cap*(sig: SignalHandler): SignalContextCapability =
## Signal context capability. Can be delegated to external components.
sig.cpp.cap
proc submit*(cap: SignalContextCapability) {.
importcpp: "Genode::Signal_transmitter(#).submit()".}
## Submit a signal to a context capability.
proc nimHandleSignal(p: pointer) {.exportc.} =
## C symbol invoked by entrypoint during signal dispatch.
cast[SignalHandler](p).cb()

39
lib/genode_cpp/signals.h Normal file
View File

@@ -0,0 +1,39 @@
/*
*
* Nim's Runtime Library
* (c) Copyright 2022 Emery Hemingway
*
* See the file "copying.txt", included in this
* distribution, for details about the copyright.
*
*/
#ifndef _NIM_SIGNALS_H_
#define _NIM_SIGNALS_H_
#include <libc/component.h>
#include <base/signal.h>
#include <util/reconstructible.h>
// Symbol for calling back into Nim
extern "C" void nimHandleSignal(void *arg);
namespace Nim { struct SignalHandler; }
struct Nim::SignalHandler
{
// Pointer to the Nim handler object.
void *arg;
void handle_signal() {
Libc::with_libc([this] () { nimHandleSignal(arg); }); }
Genode::Signal_handler<SignalHandler> handler;
SignalHandler(Genode::Entrypoint *ep, void *arg)
: arg(arg), handler(*ep, *this, &SignalHandler::handle_signal) { }
Genode::Signal_context_capability cap() { return handler; }
};
#endif

View File

@@ -281,6 +281,8 @@ proc adjustTimeout(
result = max(nextTimer.get(), 0)
result = min(pollTimeout, result)
proc runOnce(timeout: int): bool {.gcsafe.}
proc callSoon*(cbproc: proc () {.gcsafe.}) {.gcsafe.}
## Schedule `cbproc` to be called as soon as possible.
## The callback is called when control returns to the event loop.
@@ -390,7 +392,7 @@ when defined(windows) or defined(nimdoc):
let p = getGlobalDispatcher()
p.handles.len != 0 or p.timers.len != 0 or p.callbacks.len != 0
proc runOnce(timeout = 500): bool =
proc runOnce(timeout: int): bool =
let p = getGlobalDispatcher()
if p.handles.len == 0 and p.timers.len == 0 and p.callbacks.len == 0:
raise newException(ValueError,
@@ -1169,6 +1171,9 @@ else:
MSG_NOSIGNAL
when declared(posix.accept4):
from posix import accept4, SOCK_CLOEXEC
when defined(genode):
include genode/env # get the implicit Genode env
import genode/signals
const
InitCallbackListSize = 4 # initial size of callbacks sequence,
@@ -1187,6 +1192,8 @@ else:
PDispatcher* = ref object of PDispatcherBase
selector: Selector[AsyncData]
when defined(genode):
signalHandler: SignalHandler
proc `==`*(x, y: AsyncFD): bool {.borrow.}
proc `==`*(x, y: AsyncEvent): bool {.borrow.}
@@ -1202,6 +1209,10 @@ else:
result.selector = newSelector[AsyncData]()
result.timers.clear()
result.callbacks = initDeque[proc () {.closure, gcsafe.}](InitDelayedCallbackListSize)
when defined(genode):
let entrypoint = ep(cast[GenodeEnv](runtimeEnv))
result.signalHandler = newSignalHandler(entrypoint):
discard runOnce(0)
var gDisp{.threadvar.}: owned PDispatcher ## Global dispatcher
@@ -1371,10 +1382,11 @@ else:
ValueError, "Expecting async operations to stop when fd has closed."
)
proc runOnce(timeout = 500): bool =
proc runOnce(timeout: int): bool =
let p = getGlobalDispatcher()
if p.selector.isEmpty() and p.timers.len == 0 and p.callbacks.len == 0:
when defined(genode):
if timeout == 0: return
raise newException(ValueError,
"No handles or timers registered in dispatcher.")
@@ -2025,3 +2037,17 @@ when defined(linux) or defined(windows) or defined(macosx) or defined(bsd) or
if getrlimit(RLIMIT_NOFILE, fdLim) < 0:
raiseOSError(osLastError())
result = int(fdLim.rlim_cur) - 1
when defined(genode):
proc scheduleCallbacks*(): bool {.discardable.} =
## *Genode only.*
## Schedule callback processing and return immediately.
## Returns `false` if there is nothing to schedule.
## RPC servers should call this to dispatch `callSoon`
## bodies after retiring an RPC to its client.
## This is effectively a non-blocking `poll(…)` and is
## equivalent to scheduling a momentary no-op timeout
## but faster and with less overhead.
let dis = getGlobalDispatcher()
result = dis.callbacks.len > 0
if result: submit(dis.signalHandler.cap)