Skip to main content

concurrent-c-node

Call Node (and npm packages) from Python. Native types, exceptions, callbacks, and async all cross the boundary.

Part of Concurrent-C — a strict C11-superset preprocessor: .ccs lowers to plain C and compiles with your host C compiler. (This bridge itself is pure Python stdlib — no native build.)

Map of the three boundaries (CC hosts JS, native modules, this package bridge): JS / Python interop.

import cc_node

js = cc_node.create()                # always a child `node` process
_ = js.require('lodash')             # cwd node_modules
_.chunk([1, 2, 3, 4, 5], 2)          # [[1, 2], [3, 4], [5]]

semver = js.require('semver')
semver.satisfies('1.2.3', '^1.0.0')  # True

js.destroy()                         # or: with cc_node.create() as js:

Unlike concurrent-c-python (in-process by default), every create() here is a separate Node — real addons, crash isolation, measurable wire. N domains = N processes.

this package CC hosted (cc_js_new(false, …))
API cc_node.create() .ccs program
Where child node libnode in-process
Hot call ~105µs RTT sub-µs (needs libnode)
Bulk shm (~9.5ms / 8MB) in-process
Parallelism N children one process
Crash child dies; parent lives shared fate
pip install concurrent-c-node          # needs node on PATH
python -m cc_node.examples.use_node
python -m cc_node.examples.bench_wire
python -m cc_node.benchmarks.multi_domain

Measured

bench_wire · cc_node_bridge_py_20260810.txt:

what result
spawn (first eval) 28ms
wire RTT 105µs
Python callback round trip 153µs
8MB array('d') via shm 9.5ms
same 8MB as JSON list 499ms (~52×)

Wire: line-JSON on dedicated fds (stdio stays yours). Bulk spill: private 0700 dir, 0600 files, removed with the bridge. Crash isolation, not a sandbox — don’t eval untrusted JS.

Surface

  • Plain data (numbers, str, bool, None, lists, non-empty dicts) by value. Empty {} stays a live handle. Else: domain-owned handle (attrs, calls, str()String()). Non-finite floats are tagged.
  • Handles stay in one domain. stats() / release() / idempotent destroy(); after close: bridge is closed. Teardown is cooperative; CPU-bound JS isn’t cancelable — wait or kill (bridge_stress.md).

Promises

Awaited in the child before the reply — no async/await on the Python side:

fetchish = js.eval('async (x) => { return { doubled: x * 2 } }')
fetchish(21)   # {'doubled': 42}

Callbacks

mapped = js.eval('(f) => [1, 2, 3].map(f)')(lambda x, *rest: x * 10)
# map passes (value, index, array) — take *rest

Exceptions cross both ways with messages intact.

Buffers

bytes / array.array / 1-D numpy → typed arrays (and back). Small inline; large via shm.

import array
total = js.eval('(a) => a.reduce((s, x) => s + x, 0)')
total(array.array('d', range(1_000_000)))

Common issues

Cannot find module. require / import resolve from the Python process cwd (node_modules next to your program), not from this wheel’s site-packages. npm install lodash in the project directory is the fix; or create(node=…) / CC_NODE_BIN when the wrong Node is on PATH. Missing-module errors name that cwd rule.

Empty {} stays a handle

An empty object has to stay on the Node side — a materialized Python {}/dict would lose later property use that matches Node. So js.eval('({})') returns a live handle:

o = js.eval('({})')                    # JsHandle, not {}
js.eval('(o) => { o.x = 1; return o.x }')(o)   # 1
js.eval('({a: 1})')                    # {'a': 1} — data return

Non-empty plain objects still cross as Python dicts. Same-domain handles chain (h.update(…).digest(…)); foreign-domain handles do not.

Thenables settle in the child. Promise-based npm APIs need no async/await on the Python side — the call blocks until settle (or raises on reject). Opposite of concurrent-c-python isolated, where every call is already a JS Promise you must await.

Wire cost vs tiny work. Round trip is ~100µs; a one-line JS helper on three numbers loses to pure Python. Prefer Python (or a native CC module) for small/hot work; use the bridge when Node/npm owns the kernel. Multi-core: python -m cc_node.benchmarks.multi_domain (~2.8× on 3 domains here).

Crash isolation, not a sandbox. The child inherits your environment — don’t eval untrusted JS. destroy() is cooperative; CPU-bound JS is not preemptible (wait or kill + new domain).

Choosing node

  1. create(node='/path/to/node')
  2. CC_NODE_BIN
  3. node on PATH

Packages: cwd node_modules, same as Node itself.

From Concurrent-C (not Python): cc_js_new(false, &a) hosted (libnode), or cc_js_new(true, &a) for this wire — jsdemo.shcc.

Publishing

PyPI Trusted Publishing (OIDC) — no API token:

  1. Publishing settings: owner sreekotay, repo concurrent-c, workflow publish-cc-node.yml, environment pypi
  2. GitHub Environment pypi
  3. Bump pyproject.toml, push, then:
gh workflow run publish-cc-node.yml

Local / npm sibling:

./scripts/publish_bridges.sh --publish --minor
gh workflow run publish-cc-node.yml
# twine fallback: … --pypi-twine

Examples: use_node, bench_wire, benchmarks.multi_domain.
Stress: stress/bridge/.
Own hot path in C/CC → native module (40–90ns) instead of the wire — JS / Python interop.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

concurrent_c_node-0.16.0.tar.gz (16.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

concurrent_c_node-0.16.0-py3-none-any.whl (15.8 kB view details)

Uploaded Python 3

File details

Details for the file concurrent_c_node-0.16.0.tar.gz.

File metadata

  • Download URL: concurrent_c_node-0.16.0.tar.gz
  • Upload date:
  • Size: 16.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/7.0.0 CPython/3.13.14

File hashes

Hashes for concurrent_c_node-0.16.0.tar.gz
Algorithm Hash digest
SHA256 19ecff72ef6cd207cfafdb814dfed2f179a9fd573fe80c5b22c02f2b4cbc70cc
MD5 05543a33ea67398ed603ff7b27417103
BLAKE2b-256 76f3b9e8c212d7ed52b2501d4c95234821fade1652dea429eca4313b98e64e80

See more details on using hashes here.

Provenance

The following attestation bundles were made for concurrent_c_node-0.16.0.tar.gz:

Publisher: publish-cc-node.yml on sreekotay/concurrent-c

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file concurrent_c_node-0.16.0-py3-none-any.whl.

File metadata

File hashes

Hashes for concurrent_c_node-0.16.0-py3-none-any.whl
Algorithm Hash digest
SHA256 7e829bbc672dd69ece3c1f24d7ea860075f2bb999a4570d89e176288deb268d5
MD5 ce45ee797d2946d8d341f6e9c13c9e4b
BLAKE2b-256 af4e5e2b91a59acb0932007fe3bd200044d714cc191c86d48164f85e4486a09b

See more details on using hashes here.

Provenance

The following attestation bundles were made for concurrent_c_node-0.16.0-py3-none-any.whl:

Publisher: publish-cc-node.yml on sreekotay/concurrent-c

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.23.3

2 files

0.23.2

2 files

0.23.1

2 files

0.23.0

2 files

0.22.1

2 files

0.22.0

2 files

0.21.0

2 files

0.20.0

2 files

0.19.0

2 files

0.18.0

2 files

0.17.11

2 files

0.17.10

2 files

0.17.9

2 files

0.17.8

2 files

0.17.7

2 files

0.17.6

2 files

0.17.5

2 files

0.17.4

2 files

0.17.3

2 files

0.17.2

2 files

0.17.1

2 files

0.17.0

2 files

This release

0.16.0 This release

2 files

0.15.0

2 files

0.14.0

2 files

0.12.0

2 files

0.11.0

2 files

0.10.0

2 files

0.7.0

2 files

0.6.0

2 files

0.5.0

2 files

0.4.1

2 files

0.4.0

2 files

0.3.0

2 files

0.2.1

2 files

0.2.0

2 files

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page