Skip to content

About

A JVM microservice based on Python, no need to dive into Java Springboot, infinite scalability.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

Unidbg Project Host — 1.0

One deployment, many independently managed Unidbg projects. The Python edition provides FastAPI, Swagger UI at /docs, and isolated reusable Python/JPype worker processes. The host itself never starts a JVM.

中文说明 · Architecture · Migration from 0.2 · Standalone Java edition

Install and start

Requires Python 3.11+ and a compatible installed JVM (JDK 17 is tested).

pip install py_unidbg_server==1.0.0
unidbg-server start --core ./unidbg_core --projects ./projects

Open http://127.0.0.1:8888/docs. Keep one host process per runtime directory; do not use multiple Uvicorn/Gunicorn workers against the same catalog. Increase project replicas instead. The CLI uses standard asyncio on Windows and Linux.

deployment/
  unidbg_core/                 shared core JAR inputs
  projects/
    demo/
      project.jar             business code, not a standalone HTTP service
      unidbg-project.json
      resources/              APK/SO/read-only rootfs seeds
  .unidbg-runtime/             host-owned snapshots, state, writable instances

Deploy only trusted local project packages. The host is not a security sandbox. Its management API intentionally defaults to loopback and has no built-in user authentication; put an authenticated proxy in front before exposing it remotely.

Project contract

Java projects keep their own normal main(String[] args) for debugging. Expose any number of public static Object method(String payload) methods. HTTP data is serialized as JSON once before entering that String contract; results are returned as strings. No server SDK, Spring application, or per-project port is required.

{
  "version": "2026.09.1",
  "execution": {
    "replicas": 2,
    "queue_capacity": 32,
    "queue_timeout": 30,
    "call_timeout": 60,
    "idle_timeout": 300
  },
  "lifecycle": {
    "class": "com.example.Project",
    "load_method": "initialize",
    "unload_method": "close",
    "pass_project_dir": true
  },
  "apis": {
    "sign": {
      "class": "com.example.Project",
      "method": "sign",
      "description": "Sign an input using this project's emulator"
    },
    "decrypt": {
      "class": "com.example.Project",
      "method": "decrypt"
    }
  }
}

Lifecycle is optional. load_method, when present, must be static initialize(String projectDirectory) (the configured method name can differ). Use it to warm the emulator before readiness. Cleanup is static close(String projectDirectory) when pass_project_dir=true, otherwise close(). Close emulators, native handles, threads and executors there.

Each replica owns one JVM and a private writable copy of the project resources. All APIs on that replica execute serially; replicas and projects run concurrently. Do not hard-code a writable shared rootfs/cache path across replicas. Stateful sessions spanning multiple calls are not routed with affinity in 1.0: use one replica and avoid rolling updates until the session ends, or externalize state.

Calls, docs and lifecycle

curl -X POST http://127.0.0.1:8888/projects/demo/reload
curl -X POST http://127.0.0.1:8888/projects/demo/apis/sign -H "Content-Type: application/json" -d '{"data":{"value":1}}'
curl http://127.0.0.1:8888/list
curl -X DELETE "http://127.0.0.1:8888/projects/demo?timeout=30"
Endpoint Meaning
POST /call-java Legacy shape: project, class_name, optional method_name=start, data
POST /projects/{project}/apis/{alias} Call a declared alias with {"data": ...}
GET /projects/{project}/apis Cached API declarations and verification facts
GET /list, GET /health Nonblocking generation, readiness, running/queued counts
POST /projects/refresh Discover staged directories and refresh Swagger entries
POST /projects/{project}/reload Snapshot, warm, validate, and switch to a new generation
DELETE /projects/{project}?timeout=30 Persist disablement and drain accepted work
GET /docs, GET /openapi.json Generic routes plus concrete paths for every project alias

For new packages, stage the directory completely before refresh/reload. Do not modify the staging tree during snapshot creation; publish files through an operator-controlled atomic directory switch. Updating business code requires rebuilding the business JAR, not rebuilding/restarting this host.

Reload prepares every new replica before publishing its generation. Failed initialization or an invalid alias leaves the serving version unchanged. Already-accepted requests, including queued ones, stay pinned to the old version. Old resources close only after those leases drain. Allow spare process/memory budget for overlapping generations.

available: null means declared but not yet worker-verified. true means the class and public/static/String signature passed worker initialization, not that every input is business-valid. Readiness counts are separate. Reload or a first call performs verification; /list does not create a JVM per deployed directory.

Queue overflow/wait timeout returns 429. Disabled projects return 409. Java execution timeout returns 504 and discards the worker; a subsequent request can create a replacement. Worker disconnection returns 502. Submitted calls are never automatically replayed because their effects may already have occurred. Cancelling a client wait does not return an executing emulator to the pool.

A DELETE drain timeout returns 409 but remains disabled and drains in the background. Explicit reload re-enables it. Idle eviction, unlike disablement, keeps the project enabled and lazily reloadable. Idle timeout 0 disables eviction.

Settings

Use process environment variables (a .env.example is provided as a reference; the CLI does not implicitly load .env):

Variable Default
UNIDBG_RUNTIME_DIR <base>/.unidbg-runtime
UNIDBG_MAX_PROCESSES 16, including warming/draining generations
UNIDBG_STARTUP_TIMEOUT 60 seconds per replica
UNIDBG_SHUTDOWN_TIMEOUT 60 seconds for owned calls/control tasks
UNIDBG_MAX_MESSAGE_BYTES 4 MiB per HTTP body/IPC frame
UNIDBG_MAX_PROJECT_BYTES 1 GiB per project copy

Core/project/base/host/port environment settings remain available to ASGI factory users through ServerSettings.from_environment(). CLI core/project/host/port arguments take precedence. unidbg-server edit copies an editable ASGI factory.

Stopped Python worker instance directories are reclaimed after path/link checks. If a project created links, oversized output or locked files, cleanup retains the directory and logs a warning rather than traversing/retrying destructively. Immutable release snapshots are retained for diagnosis and restart; plan disk retention explicitly. Do not delete a selected or draining release. External files, subprocess trees created by business code, and external side effects are owned by the project and are not magically reclaimed by terminating one JVM.

Python API

Version 1 removes hidden global JVM state from the public API:

import asyncio
from py_unidbg_server import ServerSettings, create_manager

async def main():
    settings = ServerSettings.build()
    async with create_manager(settings) as manager:
        result = await manager.call("demo", '{"value":1}', api="sign")
        print(result)
        await manager.unload("demo")

asyncio.run(main())

Use the same manager on its owning event loop; do not create it per request. An OS file lock prevents concurrent hosts from mutating one runtime directory. The in-memory request queue is not a durable job queue: host failure can lose responses and clients must decide whether an unknown outcome is safe to retry.

Verification and distribution

pip install -e ".[dev]"
pytest -q
python -m build
python -m twine check dist/*

Tests cover units, HTTP composition, and real compiled JARs/child JVMs: multiple APIs, parallel instances, hot replacement, queue bounds, cancellation, process failure, timeout, and durable disablement. They do not prove arbitrary third-party Unidbg native backends are thread-safe or that a particular APK/SO business call is correct. Validate your real artifacts on the target OS.

The java-host/ directory is a separate JDK 17/Maven implementation. It is committed and tested on GitHub, but explicitly excluded from Python wheel/sdist and is not published to PyPI or Maven Central by this repository.

BSD-3-Clause license.

About

A JVM microservice based on Python, no need to dive into Java Springboot, infinite scalability.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages