Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
76c9666
Embedded Linux port, hardware API and a garbage-free backend fast path
shai-almog Oct 26, 2026
3748b1e
CI round 1: standalone bench build, self-contained hardware snippets,…
shai-almog Oct 26, 2026
0994875
CI round 1: lazy embedded flag for JVM tests, backend chapter anchor
shai-almog Oct 26, 2026
bd4c93d
Embedded round 2: GPU renderer, mDNS discovery, QR codes, CI fixes
shai-almog Oct 26, 2026
c0df90c
CI round 2: scanf-free CPU count, heap window opts out of huge pages,…
shai-almog Oct 26, 2026
12ca52d
Merge master into embedded-linux-port
shai-almog Oct 26, 2026
56f0c65
Bench: per-mapping idle memory breakdown; drop the huge-page opt-out …
shai-almog Oct 26, 2026
d03765a
Backend: open libcurl on the first outbound request on dynamic Linux …
shai-almog Oct 26, 2026
432dfe4
Embedded: durable outbox, PKCS#10 enrollment and client certificates
shai-almog Oct 26, 2026
1916e78
Embedded: a backend deployed to a board runs the device profile
shai-almog Oct 26, 2026
4d98989
Guide: one project from the phone to the cloud, the five connected sh…
shai-almog Oct 26, 2026
3905386
Core: MQTT 3.1.1 client over TCP and WebSocket, QoS 0/1
shai-almog Oct 26, 2026
cc9ddba
Linux port: NFC tag reading over PC/SC, loaded on first use
shai-almog Oct 26, 2026
3372527
Bench: report the server's exit status and log when the exit census i…
shai-almog Oct 26, 2026
e28aa5d
Merge remote-tracking branch 'origin/master' into embedded-linux-port
shai-almog Oct 26, 2026
3b092e4
Guide snippets for the outbox and mTLS move to docs/demos; copyright …
shai-almog Oct 26, 2026
18486de
Bench: the census build keeps libcurl linked; its exit census vanishe…
shai-almog Oct 26, 2026
e36f5e4
MQTT: PMD forbidden-rule fixes; accept enqueues and backoff in the guide
shai-almog Oct 27, 2026
55c30bf
Guide: the connected-device snippets are not Playground-runnable
shai-almog Oct 27, 2026
24ff834
Outbox and CertificateRequest javadoc examples are not Playground-run…
shai-almog Oct 27, 2026
68cb817
mTLS never trusts every server: a client certificate on an insecure r…
shai-almog Oct 27, 2026
201212d
Merge remote-tracking branch 'origin/master' into embedded-linux-port
shai-almog Oct 27, 2026
0cc8681
Merge remote-tracking branch 'origin/master' into embedded-linux-port
shai-almog Oct 27, 2026
b3c1e00
Backend mDNS: the RFC 6762 multicast group is a named constant (PMD A…
shai-almog Oct 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 120 additions & 0 deletions .github/workflows/backend-performance.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Holds the Codename One backend to its headless benchmark results.
#
# Builds the bench demo (vm/backend/demo/bench) twice: as shipped, and with the
# allocation census compiled in. The census run counts what each request
# allocates, exactly. The shipped binary then takes turns with Rust's axum and
# Go's net/http on the same two cores of an arm64 runner, through
# scripts/embedded/bench/headless/run.sh, the harness behind the developer
# guide's tables. gate.py judges speed against Rust from the same job and memory
# and allocation absolutely; its docstring says why, and where the limits live.
#
# The job uploads the raw results, the census and charts drawn from this run, so
# the guide's numbers can be refreshed from a CI machine rather than a laptop.
name: Backend performance gate

on:
workflow_dispatch:
push:
branches: [master]
paths:
- 'vm/backend/**'
- 'vm/ByteCodeTranslator/src/**'
- 'vm/JavaAPI/src/**'
- 'scripts/embedded/bench/charts.py'
- 'scripts/embedded/bench/headless/**'
- '.github/workflows/backend-performance.yml'
pull_request:
paths:
- 'vm/backend/**'
- 'vm/ByteCodeTranslator/src/**'
- 'vm/JavaAPI/src/**'
- 'scripts/embedded/bench/charts.py'
- 'scripts/embedded/bench/headless/**'
- '.github/workflows/backend-performance.yml'

concurrency:
group: backend-performance-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
gate:
# Four cores: the servers get 0-1 and the load generator 2-3.
runs-on: ubuntu-24.04-arm
timeout-minutes: 90
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up JDK 8
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '8'
cache: 'maven'

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

# Go, Rust, oha and the comparison servers; cached, since none of it
# depends on this repository's code.
- name: Build the benchmark image
uses: docker/build-push-action@v6
with:
context: scripts/embedded/bench/headless
file: scripts/embedded/bench/headless/Containerfile
tags: cn1-headless:ci
load: true
cache-from: type=gha,scope=backend-performance
cache-to: type=gha,mode=max,scope=backend-performance

- name: Build the bench demo, shipped and with the census
working-directory: vm/backend
env:
JDK_8_HOME: ${{ env.JAVA_HOME }}
CN1_BACKEND_DEMO: demo/bench
CN1_BACKEND_ENGINE: docker
# The bench demo needs neither the shared contract nor demo/common.
CN1_BACKEND_STANDALONE_DEMO: "1"
run: |
mkdir -p "$RUNNER_TEMP/dist"
./package.sh Bench com.demo glibc-arm64
cp target/dist/bench-linux-glibc-arm64 "$RUNNER_TEMP/dist/bench"
CN1_BACKEND_CFLAGS="-DCN1_ALLOC_CENSUS -DCN1_HEAP_HISTOGRAM" \
./package.sh Bench com.demo glibc-arm64
cp target/dist/bench-linux-glibc-arm64 "$RUNNER_TEMP/dist/bench-census"

- name: Count what a request allocates
run: |
mkdir -p "$RUNNER_TEMP/out"
docker run --rm -v "$RUNNER_TEMP/dist:/dist:ro" \
-v "$GITHUB_WORKSPACE/scripts/embedded/bench/headless/census.sh:/census.sh:ro" \
cn1-headless:ci /census.sh /dist/bench-census 200000 | tee "$RUNNER_TEMP/out/census.txt"

# Privileged so run.sh can drop the page cache before each cold start.
- name: Run the comparison
run: |
docker run --rm --privileged -e RUNTIMES="codenameone rust go" \
-v "$RUNNER_TEMP/dist:/dist:ro" \
-v "$GITHUB_WORKSPACE/scripts/embedded/bench/headless/run.sh:/bench/run.sh:ro" \
cn1-headless:ci /bench/run.sh /dist/bench 3 | tee "$RUNNER_TEMP/out/results.txt"

- name: Draw the charts
run: |
mkdir -p "$RUNNER_TEMP/out/img"
python3 scripts/embedded/bench/charts.py "$RUNNER_TEMP/out/img" "$RUNNER_TEMP/out/results.txt"

- name: Judge the results
run: |
python3 scripts/embedded/bench/headless/gate.py "$RUNNER_TEMP/out/results.txt" \
"$(cat "$RUNNER_TEMP/out/census.txt")" "$GITHUB_STEP_SUMMARY"

- name: Upload the results
if: always()
uses: actions/upload-artifact@v4
with:
name: backend-performance
path: ${{ runner.temp }}/out
if-no-files-found: ignore
128 changes: 128 additions & 0 deletions .github/workflows/embedded-linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Builds and runs the embedded Linux port (CN1_LINUX_EMBEDDED: DRM/KMS, fbdev or
# memory display, evdev input, no GTK) the way scripts/embedded/README.md tells a
# user to: translate scripts/embedded/demo with translate.sh, build it for arm64
# with build-dist.sh in Containerfile.dev, and run it on the memory display.
#
# check-demo-run.py then fails the job when frame 60 is missing, wrong or blank,
# or when the speed and memory the developer guide reports have slipped. The
# generated stub file is checked against the natives too, since a native added to
# LinuxNative without a stub breaks only the embedded link.
#
# The GPU renderer (CN1_EMBEDDED_GPU, GLES2 through GBM/EGL) runs on Mesa's
# llvmpipe, since the runner has no GPU: gpu-check draws every kind of operation
# both ways and fails on a differing pixel, and the demo is built a second time
# with the GPU renderer, whose frame 60 must match the software run's exactly.
# llvmpipe says nothing about a board's GPU speed; it holds correctness.
name: Embedded Linux

on:
workflow_dispatch:
push:
branches: [master]
paths:
- 'Ports/LinuxPort/**'
- 'CodenameOne/src/**'
- 'vm/ByteCodeTranslator/src/**'
- 'vm/JavaAPI/src/**'
- 'maven/linux/**'
- 'scripts/embedded/**'
- '.github/workflows/embedded-linux.yml'
pull_request:
paths:
- 'Ports/LinuxPort/**'
- 'CodenameOne/src/**'
- 'vm/ByteCodeTranslator/src/**'
- 'vm/JavaAPI/src/**'
- 'maven/linux/**'
- 'scripts/embedded/**'
- '.github/workflows/embedded-linux.yml'

concurrency:
group: embedded-linux-${{ github.ref }}
cancel-in-progress: true

permissions:
contents: read

jobs:
demo:
runs-on: ubuntu-24.04-arm
timeout-minutes: 75
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: The embedded stubs match the natives
run: python3 scripts/embedded/gen-embedded-stubs.py --check

- name: Set up JDK 8
uses: actions/setup-java@v5
with:
distribution: 'temurin'
java-version: '8'
cache: 'maven'

# Retried: Maven Central answers 429 from the runner CDN edge (see
# linux-build-run.yml). This step only builds.
- name: Build core, the Linux port, JavaAPI and the translator
env:
RETRY_ATTEMPTS: '5'
RETRY_DELAY_SECONDS: '60'
run: |
cd maven
bash ../scripts/ci/retry.sh mvn -B -pl core,linux -am install \
-DskipTests -Dmaven.javadoc.skip=true -Plocal-dev-javase \
-Dspotbugs.skip=true -Dpmd.skip=true -Dcheckstyle.skip=true \
-Dmaven.repo.local="$GITHUB_WORKSPACE/.m2-repo"
cd ..
bash scripts/ci/retry.sh mvn -B -f vm/pom.xml -pl JavaAPI,ByteCodeTranslator -am package \
-DskipTests -Dmaven.repo.local="$GITHUB_WORKSPACE/.m2-repo"

- name: Translate and build the demo for arm64
run: |
B="$RUNNER_TEMP/demo"
mkdir -p "$B/classes"
javac -source 1.8 -target 1.8 -cp maven/core/target/classes:maven/linux/target/classes \
-d "$B/classes" scripts/embedded/demo/src/EmbeddedDemo.java
scripts/embedded/translate.sh "$B/classes" EmbeddedDemo "$B/out"
scripts/embedded/build-dist.sh "$B/out" arm64

- name: Run the demo on the memory display
run: |
B="$RUNNER_TEMP/demo"
mkdir -p "$B/result"
docker run --rm -v "$B/out/build-arm64:/cn1:ro" -v "$B/result:/result" cn1-embedded-dev:arm64 \
bash -c 'cd /tmp && CN1_DISPLAY=memory CN1_SIZE=800x480 CN1_BENCH_SECONDS=10 \
CN1_SNAPSHOT=/result/frame60.png timeout 120 /cn1/EmbeddedDemo > /result/demo.log 2>&1'
python3 scripts/embedded/check-demo-run.py "$B/result/demo.log" "$B/result/frame60.png" \
"$GITHUB_STEP_SUMMARY"

- name: The GPU renderer draws what the software one draws
run: scripts/embedded/gpu-check/check-gpu.sh arm64 "$RUNNER_TEMP/demo/result/gpu-check"

# Same translation, rebuilt with the GPU renderer into the same build
# directory; the software run above has already been judged.
- name: Run the demo with the GPU renderer
env:
CN1_CMAKE_ARGS: -DCN1_EMBEDDED_GPU=ON
run: |
B="$RUNNER_TEMP/demo"
scripts/embedded/build-dist.sh "$B/out" arm64
mkdir -p "$B/result/gpu"
docker run --rm -e LIBGL_ALWAYS_SOFTWARE=1 -v "$B/out/build-arm64:/cn1:ro" \
-v "$B/result/gpu:/result" cn1-embedded-dev:arm64 \
bash -c 'cd /tmp && CN1_DISPLAY=memory CN1_SIZE=800x480 CN1_BENCH_SECONDS=10 \
CN1_SNAPSHOT=/result/frame60.png timeout 120 /cn1/EmbeddedDemo > /result/demo.log 2>&1'
grep -q "renderer gpu" "$B/result/gpu/demo.log" \
|| { echo "the GPU build did not render with the GPU"; tail -20 "$B/result/gpu/demo.log"; exit 1; }
python3 scripts/embedded/check-demo-run.py --max-rss-mb 160 \
--reference "$B/result/frame60.png" "$B/result/gpu/demo.log" "$B/result/gpu/frame60.png" \
"$GITHUB_STEP_SUMMARY"

- name: Upload the run
if: always()
uses: actions/upload-artifact@v4
with:
name: embedded-demo
path: ${{ runner.temp }}/demo/result
if-no-files-found: ignore
8 changes: 8 additions & 0 deletions .github/workflows/parparvm-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ on:
# marker; a class marked outside these directories needs its path here.
- 'CodenameOne/src/com/codename1/impl/SharedWithBackend.java'
- 'CodenameOne/src/com/codename1/impl/orm/**'
- 'CodenameOne/src/com/codename1/hardware/**'
- 'CodenameOne/src/com/codename1/qrcode/QrCode.java'
- 'CodenameOne/src/com/codename1/impl/MdnsEngine.java'
- 'CodenameOne/src/com/codename1/orm/session/**'
- 'CodenameOne/src/com/codename1/orm/repository/**'
- 'CodenameOne/src/com/codename1/annotations/db/**'
Expand All @@ -53,6 +56,7 @@ on:
- 'CodenameOne/src/com/codename1/annotations/JsonIgnore.java'
- 'CodenameOne/src/com/codename1/annotations/JsonProperty.java'
- 'CodenameOne/src/com/codename1/io/gzip/**'
- 'CodenameOne/src/com/codename1/io/outbox/**'
- 'CodenameOne/src/com/codename1/migration/**'
- 'CodenameOne/src/com/codename1/impl/migration/**'
- 'CodenameOne/src/com/codename1/security/Hash.java'
Expand Down Expand Up @@ -108,6 +112,9 @@ on:
# marker; a class marked outside these directories needs its path here.
- 'CodenameOne/src/com/codename1/impl/SharedWithBackend.java'
- 'CodenameOne/src/com/codename1/impl/orm/**'
- 'CodenameOne/src/com/codename1/hardware/**'
- 'CodenameOne/src/com/codename1/qrcode/QrCode.java'
- 'CodenameOne/src/com/codename1/impl/MdnsEngine.java'
- 'CodenameOne/src/com/codename1/orm/session/**'
- 'CodenameOne/src/com/codename1/orm/repository/**'
- 'CodenameOne/src/com/codename1/annotations/db/**'
Expand All @@ -118,6 +125,7 @@ on:
- 'CodenameOne/src/com/codename1/annotations/JsonIgnore.java'
- 'CodenameOne/src/com/codename1/annotations/JsonProperty.java'
- 'CodenameOne/src/com/codename1/io/gzip/**'
- 'CodenameOne/src/com/codename1/io/outbox/**'
- 'CodenameOne/src/com/codename1/migration/**'
- 'CodenameOne/src/com/codename1/impl/migration/**'
- 'CodenameOne/src/com/codename1/security/Hash.java'
Expand Down
87 changes: 87 additions & 0 deletions CodenameOne/src/com/codename1/hardware/CanBus.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Codename One designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Codename One through http://www.codenameone.com/ if you
* need additional information or have any questions.
*/
package com.codename1.hardware;

/// A CAN network interface, opened through [Hardware#openCan(String)].
@com.codename1.impl.SharedWithBackend
public final class CanBus {

private final Hardware hw;
private final int handle;
private final String iface;
private boolean closed;

CanBus(Hardware hw, int handle, String iface) {
this.hw = hw;
this.handle = handle;
this.iface = iface;
}

/// The interface name.
public String getInterface() {
return iface;
}

/// Sends one frame.
///
/// #### Throws
///
/// - `HardwareException`: when the send fails or the bus is closed
public void send(CanFrame frame) throws HardwareException {
checkOpen();
hw.canSend(handle, frame.getId(), frame.isExtended(), frame.getData(), frame.getLength());
}

/// Waits up to `timeoutMs` for a frame (negative waits indefinitely) and
/// returns it, or `null` on timeout. Call it off the event dispatch thread.
///
/// #### Throws
///
/// - `HardwareException`: when the receive fails or the bus is closed
public CanFrame receive(int timeoutMs) throws HardwareException {
checkOpen();
int[] id = new int[2];
byte[] data = new byte[8];
int len = hw.canReceive(handle, id, data, timeoutMs);
if (len < 0) {
return null;
}
byte[] payload = new byte[len];
System.arraycopy(data, 0, payload, 0, len);
return new CanFrame(id[0], id[1] != 0, payload);
}

/// Closes the interface. Closing twice is harmless.
public void close() {
if (!closed) {
closed = true;
hw.close(handle, false);
}
}

private void checkOpen() throws HardwareException {
if (closed) {
throw new HardwareException("CAN interface " + iface + " is closed");
}
}
}
Loading
Loading