This repository contains a research prototype of Nextless, a "next-generation" serverless platform for edge computing scenarios.
Nextless relies on
- compiler-inspired transformations and optimizations in the orchestration system (orchestration-time abstractions),
- the integration of resources such as sensors and actuators,
- and the use of lightweight runtimes,
to enable the efficient execution of complex serverless applications on sets of heterogeneous and potentially resource-constrained edge devices.
Nextless is a research fork of the EDGELESS reference implementation.
The system is in an early work-in-progress state and should not be used for anything beyond experimentation.
The system does not contain any security features and should only be used in fully trusted networks.
Nextless executes Applications comprising Actors and Resources.
Actors represent stateful but side-effect-free services defined by untrusted developers.
Due to their untrusted nature, they are executed in an isolated way using suitable isolation techniques, e.g., process VMs.
Resources are included in the implementation of the worker nodes and can only be configured and instantiated.
They are executed without any sandbox and are thereby able to cause external effects and may be triggered by external effects.
Both Actors and Resources define a set of input and output Ports.
Those are mapped together in the Application Description, enabling the reuse of Actors across applications.
Both the Actor's logic (Behavior [1]) as well as the Interactions between Ports can be defined using a set of formats called Dialects [2].
The platform's orchestration system can convert between these dialects.
The conversion of the Actor's logic allows the system to efficiently target heterogeneous worker nodes, e.g., using platform-specific native images.
The conversion of Interactions enabled the developers to use abstractions that are then efficiently mapped to the available links.
As an example, the developers may use pub/sub, which is then automatically converted to IP Multicast.
In addition to the conversions, the system can use the holistic view provided by the application description for optimizations.
As an example, it can use static analysis to detect and remove unused Interactions, Actors, and parts of the Actors before they are deployed onto the worker nodes.
In addition to the static optimizations, the system also tracks the runtime performance of the Actor instances and uses this knowledge for adaptive optimizations.
[1]: Term inspired by the Actor model.
[2]: Term inspired by MLIR.
You can find the discussion of an example application below.
You can find additional documentation in the following documents:
- Qickstart Guide
- Installation
- Actor Reference
- Application Reference
- System Overview
- Controller (containing the Compiler-Inspired Aspects)
- Node
The figure above shows an example Application. Its code can be found here.
It consists of
- the Sensor Actor producing mocked sensor values,
- the Filter Actor that filters those sensor values and forwards them to one of two output ports depending on whether their value is below a threshold,
- the Dashboard Actor that collects the last 10 values and makes them available to an HTTP API,
- the Processing Actor that performs some mocked calculations on the values before forwarding them, and
- a HTTP Ingress Resource that enabled the Dashboard's API to be reachable using actual HTTP requests.
The Resource is required as Actors, such as the Dashboard, aren't allowed to directly cause any side effects and therefore are unable to directly respond to HTTP requests.
In the example, the Processor's output is ignored, which renders its execution useless.
Transitively, this also renders the events going from the Filter to the Processor useless.
The system should detect this and elide the deployment of the Processor.
Actors are defined using an abstract definition based on Starlark:
DemoFilter = edgeless_actor_class(
id = "demo_filter",
version = "0.1",
outputs = [
cast_output("accepted_out", "eft.eval.mock_sensor_value"),
cast_output("rejected_out", "eft.eval.mock_sensor_value")
],
inputs = [
cast_input("data_in", "eft.eval.mock_sensor_value")
],
inner_structure = [link("data_in", ["accepted_out", "rejected_out"])],
code = file("demo_filter.tar.gz"),
code_type = "RUST_NO_STD"
)
el_main = DemoFilterThis definition defines the Actor's Ports (including their semantics and type), its inner structure, which is required to remove unnecessary interactions, and links to the Actor's Behavior.
In the example, the Actor's Behavior is defined using Rust (the full version can be found here):
#![no_std]
use edgeless_function::*;
struct DemoFilter;
edgeless_function::generate!(DemoFilter);
impl DemoFilterAPI<'_> for DemoFilter {
type EFT_EVAL_MOCK_SENSOR_VALUE = edgeless_function_types::eval::MockSensorValue;
fn handle_cast_data_in(_src: InstanceId, measurement: Self::EFT_EVAL_MOCK_SENSOR_VALUE) {
log::info!("Received data: value: {}", measurement.value);
if measurement.value <= 25.0 {
cast_accepted_out(&measurement);
} else {
cast_rejected_out(&measurement);
}
}
...
}Our framework generates a trait from a JSON representation of the abstract definition presented above. The developers need to implement this trait to define the Behavior of their Actor.
While implementing this trait, the developers need to map the abstract types to a Rust type implementing our framework's serialization and deserialization traits and implement handlers for the incoming ports, e.g., handle_cast_data_in.
They can use generated functions for sending data to output ports, e.g., cast_accepted_out.
To learn more about building Actors, please refer to documentation/building_actors.md.
The set of Actors/Resources constituting an application is defined in an Application Specification:
load("../../functions/demo_sensor/demo_sensor.star", "DemoSensor")
...
sensor = edgeless_actor(...)
filter = edgeless_actor(
id = "filter",
klass = DemoFilter,
annotations = {},
)
ingress = edgeless_resource(
id = "demo_ingress",
klass = HTTPIngress,
configurations = {
"host": "demo.localhost",
"methods": "GET"
}
)
processor = edgeless_actor(...)
sensor.value >> filter.data_in
ingress.new_request >> dashboard.http_fetch
filter.accepted_out >> topic("valid_measurements")
dashboard.data_in << topic("valid_measurements")
# Useless Interaction
processor.data_in << topic("valid_measurements")
wf = edgeless_workflow(
"sensor_dashboard_demo",
[sensor, filter, dashboard, ingress, processor],
annotations = {}
)
el_main = wf
This specification defines logical instances of Actors and Resources (which also require configuration). Furthermore, it defines the Interactions between the Actors.
In the example, the Sensor is directly linked to the Filter [3], while the Filter's output and the Dashboard's input are mapped together using a topic abstraction that is resolved in the orchestration system.
To learn more about building Applications, please refer to documentation/building_applications.md.
[3]: The arrow syntax has been inspired by Apache Airflow.
This repository mostly consists of folders representing Rust crates (edgeless_*).
The main system components (cf. documentation/system_components.md) can be found in the following folders/crates:
edgeless_conhouses the controller implementing the compiler-inspired aspects of the project.edgeless_cliprovides the CLI tool to build Actors and interact with the controller.edgeless_nodehouses the implementation of the worker node.
The repository contains a set of example Applications that can be deployed onto a Nextless cluster.
The Applications and their components can be found in the following folders:
functionscontains the set of Rust-based example Actors included with this repository.resourcesprovides the descriptions of the Resources included with the (embedded) worker node.examplescontains the example Applications using those Actors and Resources.
There is a set of crates that define the APIs enabling the components to interact.
edgeless_apiprovides the main APIs used for the interaction between the system components.- It also defines shared types used across the components.
egeless_api_corerepresents the parts of the APIs usable by the#![no_std]embedded node.
The crates in this category define the APIs used to implement Rust-based Actors.
They enable those Actors to interact with the worker node.
edgeless_functionrepresents the main crate used by every function.- It uses the procmacro-crate
edgeless_function_macroto provide theedgeless_function::generate!()macro.
- It uses the procmacro-crate
edgeless_function_typescontains shared type definitions for messages sent between Actors.edgeless_function_gpuprovides the actor-side implementation of our wgpu-based GPU support.edgeless_actor_abirepresents the ABI used between the native Actors and the native runtime.
edgeless_buildprovides the functionality used to build images (Wasm and native) from Rust-based Actors.- It is used by
edgeless_cliandedgeless_con. - It relies on a nightly Rust compiler.
- It is used by
edgeless_configdefines the Starlark-based description of Actors and Applications.
This repository also contains an experimental port of the worker node to microcontroller-based devices.
edgeless_embeddedrepresents the core of the embedded effort and contains the device-independent implementation of the embedded worker node.edgeless_embedded_esp32contains the ESP32(-S3) specific wrapper of the coreedgeless_embeddedand the device-specific sensor/actuator implementation.edgeless_embedded_emucontains a wrapper of the coreedgeless_embeddedthat can be executed by Linux-based nodes. It relies onembassy-net-tuntap.
EDGELESS and earlier versions of this project call Applications Workflows. We have renamed this as our Applications are different from workflows in other systems. This renaming effort is not yet complete, and the code, as well as some commands (e.g., edgeless_cli workflow start), still use the old name.
EDGELESS and earlier versions of this project call Actors Functions. We have started to rename this to Actors as this better reflects their functionality. This renaming effort is not yet complete, and the code, as well as some commands (e.g., edgeless_cli function start), still use the old name.
The Repository is licensed under the MIT License. Please refer to LICENSE and the copyright headers in each file.
This project started as a fork of the EDGELESS Reference Implementation and is developed as part of the EDGELESS Project.
The fork's main focus and differentiating factor is the integration of compiler-inspired transformations, which also require a more well-defined application model.
The fork's initial focus lies on independent and locally-aggregated clusters of devices, with support for multi-cluster deployments and cloud backends deferred for later. Compared to the EDGELESS reference implementation, Nextless simplifies the control plane by merging the controller and orchestrator, and will rely on the federation of controllers and the deployment of sub-applications to span multiple clusters.
EDGELESS received funding from the European Health and Digital Executive Agency (HADEA) program under Grant Agreement No 101092950.

