Reference

Concurrency and reloading

One thread runs an event loop (kqueue on macOS, epoll on Linux) over every connection, reads and parses requests, runs the program on them and writes the answers. Each request runs as a coroutine with its own stack. A handler runs on its own from start to end, except while it waits: a database query, http.* or sleep pauses it, the waiting is done elsewhere (on a pool thread, or for PostgreSQL on connections all requests share, with queries pipelined, and a sleep of 5 ms or more on a timer, holding no thread), and the loop answers other requests meanwhile. Code is written as if it simply waited; there is no async or await. Updating shared state such as todos = todos | push(t) is safe without locks, since only one handler runs at a time.

A request that only computes never leaves the loop's thread, so plain pages and JSON cost a few microseconds each. To use more cores, start the program with --instances N, quillo run main.qwe --instances 4, which runs the program N times over in one process, each with an event loop of its own; the first takes the connections and hands them out in turn. Like N processes, each instance has its own variables, while the database is shared, and changes to it (and publish) reach live pages on every instance. SQLite in memory (:memory:) is one database per instance, so use a file or PostgreSQL to share data between them.

What holds up the loop is code that computes for long without waiting: a loop over a million rows keeps every other request on its instance waiting until it ends. Quillo does not break into it, since that would end the promise above (a handler runs on its own between waits). Images, crypto.hash_password, files and Rust (.qwer) functions run off the loop, and sorting and stats, vec and math on large lists are spread over the cores. So put long computing in Rust (see Rust), or run more instances: with several, connections are taken on a thread of their own and go to an instance that is not computing, so one stuck instance holds up only the connections already on it.

An instance computing for 5 s is logged, with its request. Started with --max-compute 2s (quillo run main.qwe --max-compute 2s), the server ends a request that computes that long without letting go: a 500, between two statements, so nothing is left half done, and a line in the log. highlight, markdown and markdown_page end at it too, inside a long text, and their end is not an error value a ?? takes, as a loop's is not. Time spent waiting does not count. Compiled code has nowhere to stop, so under --max-compute functions are not compiled. quillo check warns on a while that cannot end.

How long a wait may last:

What a server is made of, for sizing a machine: the event loop's thread (a 16 MiB stack; each request's coroutine reserves as much, and grows as it needs), up to --workers plus one waiting threads per instance (so --instances 4 --workers 64 may reach 260 threads), one thread for PostgreSQL's pipelined connections (half the instances, at least two, with --instances), a pool of one less than the cores (at most 31) for long lists, up to four for images, one for scheduled jobs, one for calls made later, one for --watch, and one for each channel and --with command. Memory is not limited by anything configurable: the allocator is mimalloc on the loops and the waiting threads, the system's elsewhere.

Each page with live data is a thread with 256 KiB of stack reserved, of which it touches little, and two file descriptors, a socket and a connection. Quillo takes all the descriptors the system allows when it starts, and says so in the log if that is too few for 4096 pages; ulimit -n, or LimitNOFILE= under systemd, raises what is allowed.

With --watch (quillo run main.qwe --watch), a change to the program or a used file reloads it (starting its state afresh) and refreshes open pages. --reload reloads it the same way without --watch's development aids (errors on pages, refreshing them), for production programs that change their own files, such as a plugin editor. Both watch the main file and every file the program loaded (used, mounted, a service's; under --watch, files served with file() too): on macOS and Linux the system tells of a change as it happens (kqueue, inotify), and a save, in place or as an editor does it, a new file renamed over the old, is seen within a few milliseconds; elsewhere, or when the system cannot watch a file (too many open, a network mount that tells nothing), the files' modification times are looked at every 50 ms, and said so once in the log. No folder is watched, so a new file is noticed once something loads it. A version that fails to load or check does not replace the one that works; the errors are printed. A reload parses only the files that changed (the others' parses are kept) and, under --watch, compiles the program for the browser on a thread of its own once the new version answers, so a save shows in open pages about 120 ms later for a program of a few hundred components (the site quillolang.org, measured), plus the time to parse the file that changed.

What a reload keeps, and what it starts afresh:

Stopping and health

The first Ctrl-C or SIGTERM stops the server gracefully: it takes no new connections, closes idle ones, answers the requests in progress (for up to 10 seconds) and exits with status 0. A server that joined a pool with --register (see Several servers) leaves it first, so limer stops sending it requests. A second Ctrl-C stops it at once. It stops the same when its stderr goes into a pipe that has closed (a tee or a log shipper that ended): Quillo's own messages are then dropped, and never keep it from stopping.

GET /_quillo/health answers 200 (ok) while the server is ready for requests, and 503 (unavailable) while it is stopping or its program failed to load: give it to a load balancer's health check (limer's health_uri). GET /_quillo/ready also says whether what the program needs is there: { "ready": true, "checks": { "database": "ready", "service billing": "ready", "ledger": "not ready" } }, 200 or 503, its database (select 1), each service it declares (see Services) and the program's own checks, ready("ledger", () => ...) at the top level, a function that gives true. For an orchestrator's readiness probe. Neither request is logged or counted.

GET /_quillo/health/queries says how long database queries take: the minimum, maximum and average of the last 100, with how many ran and failed in all:

{ "enabled": true, "window": 100,  "queries": { "count": 100, "min_ms": 0.21, "max_ms": 48.7, "avg_ms": 3.4, "total": 5120, "errors": 2 },  "statements": [ { "sql": "select * from orders where customer = ?", "count": 100, "min_ms": 1.9, "max_ms": 48.7, "avg_ms": 12.3, "total": 840, "errors": 0 }, ... ] }

On a server already running, with the token in its environment:

export QUILLO_HEALTH_TOKEN=0123456789abcdef-token        # 16 characters at leastquillo run main.qwe                                      # or with --query-stats, on from the startcurl -X POST 'localhost:8080/_quillo/health/queries?on=1' -H "Authorization: Bearer $QUILLO_HEALTH_TOKEN"curl localhost:8080/_quillo/health/queries -H "Authorization: Bearer $QUILLO_HEALTH_TOKEN"   # with statementscurl localhost:8080/_quillo/health/queries                                                    # the totals only

Without the switch, the address says how to turn it on: "how": "off: run with --query-stats or QUILLO_QUERY_STATS=1, or POST /_quillo/health/queries?on=1 with the health token".

A second Ctrl-C, or a second SIGTERM, exits at once with status 130. Stopping also stops the commands run with --with, and a stream's send fails once the server is stopping, so a long stream ends.

Scheduled jobs

schedule("5m", clean_sessions)                  // every 5 minutes, on the clockschedule("0 3 * * *", nightly_report)           // cron: 03:00 UTCschedule("@daily", () => db.exec("delete from tokens where expires < ?", now()))

A job is a function of the program, so it takes with as any function does, and what it may do is held to that each time it runs:

fn nightly_report() -> map with db, time {    db.exec("insert into reports (day, total) select date(?), sum(total) from orders", now())}schedule("0 3 * * *", nightly_report)

What it does is seen where it writes. Here the nightly report the routes add to, and the page that shows its rows:

db.open("app.db")db.exec("create table if not exists orders (id integer primary key, total int, at bigint)")db.exec("create table if not exists reports (day text primary key, orders int, total int)")fn nightly_report() -> map {    let day = str(date.today())    let r = db.one("select count(*) as n, coalesce(sum(total), 0) as total from orders where at > ?", now() - 86_400_000)    db.exec("insert into reports (day, orders, total) values (?, ?, ?) on conflict (day) do update set orders = excluded.orders, total = excluded.total", day, r.n, r.total)}schedule("0 3 * * *", nightly_report)route POST /orders { form: { total: int } } with db, time = db.exec("insert into orders (total, at) values (?, ?)", total, now())route GET /reports with db = <ul>{for r in db.query("select * from reports order by day desc") => <li>{r.day}: {r.orders} orders, {r.total}</li>}</ul>

Each run is a line in the log, POST /_quillo/job/nightly_report?at=1793631366 -> 200 (3.5ms), and /reports shows 2026-11-02: 1 orders, 40 after it. To try a job without waiting for 03:00, give it "10s" while you work on it.

Ticks: every on the server

let ball = { x: 0, dx: 4 }fn step() {    let dx = ball.x + ball.dx > 300 || ball.x + ball.dx < 0 ? -ball.dx : ball.dx    ball = { x: ball.x + dx, dx }}fn snapshot() -> map = { x: ball.x }fn tick() { step(); channel.broadcast("room", snapshot()) }every(50, tick)                                 // 20 times a second, on the clockcomponent Court() {                             // the other side: each page in "room"    state x = 0    channel.open("room", (s) => { x = s.x })    <svg width="320" height="40"><circle cx={x + 10} cy="20" r="8"/></svg>}route GET / = <html><body><Court/></body></html>

Every page open on / shows the same ball, moved by the server 20 times a second; a page opened later picks it up where it is.

The REPL

quillo repl (or quillo alone) runs code as you type it, and prints each expression's value:

quillo> let xs = [1, 2, 3]quillo> xs | map((x) => x * 10)[10, 20, 30]quillo> fn double(n) {....>   return n * 2....> }quillo> double(21)42quillo> db.open("app.db")quillo> db.query("select * from notes")[{ id: 1, text: "first" }]

Scripts as tools

A program with no routes is a script: quillo run tool.qwe runs it from the top and ends. What follows -- is its arguments, args(); stdin() is all that is piped in, and read_line() the next line, null at the end; exit(n) ends it with status n (since 0.7). A script that ends by itself ends with 0, and one that fails (a stop, or an error a ? at the top passes on) with 1, saying what it was. Files are the program's folder's, as for a server; --allow-path DIR, given when the script is started (quillo run tool.qwe --allow-path ../data -- x), names another folder that read_file and write_file may use, by its own path or through .., where a link leads being what counts (FEEDBACK29.md, Q-357); without it, a link out of the folder is refused too (see Paths and links). It is named, never assumed: the same program may serve strangers later, and in a program that serves, stdin, read_line and exit are errors. A script whose output pipe closes (quillo run tool.qwe | head -1) ends quietly, with status 141, as other tools do; a server whose stdout closes goes on answering, and what print writes is dropped.

// cat orders.csv | quillo run total.qwe -- EURlet currency = args()[0] ?? "USD"let total = 0mlet line = read_line()while line != null {    let parts = split(line, ",")    total += dec(parts[2]) ?? 0m    line = read_line()}print(`${total} ${currency}`)exit(total > 0m ? 0 : 3)

Written the way each is usually written, a script is faster than Python's: bench/vs-python/run.py times eighteen everyday programs in both, process start included, and Quillo is ahead in all of them. Against Python 3.9 on a 10-core Mac, the best of 21 runs each:

PythonQuillo
fib(35)1.11 s0.05 s
a float loop of 50 million1.71 s0.04 s
a for summing 5 million0.14 s0.10 s
filter, map, sum over 3 million0.09 s0.07 s
a million xs = xs | push(i)0.04 s0.01 s
a map of lists, 100,000 pushes0.02 s0.02 s
a grid of 1,000 by 1,000, 100,000 writes0.02 s0.01 s
a field read a million times0.03 s0.02 s
300,000 JSON rows grouped0.17 s0.14 s
word frequency over 1.2 million words0.21 s0.15 s
a million counts by an int key0.08 s0.03 s
a sieve to 2 million0.09 s0.07 s
s += "x" a million times0.04 s0.01 s
s[i] over 300,000 characters0.02 s0.01 s
a while over 200,000 characters0.02 s0.01 s

The closest are a map of lists (0.85 of Python's time), 300,000 JSON rows grouped (0.81) and the sieve (0.79); loops over numbers, lists of them, texts and counts run in machine code (see Compiled numbers).

Debugging

quillo dap is a debugger for editors: it speaks the Debug Adapter Protocol on stdin and stdout, and runs the program as quillo run would. In VS Code, with the extension, press F5 in a .qwe file (or Quillo: Debug this file, or the ▶ menu). A launch.json entry gives the options:

{  "type": "quillo",  "request": "launch",  "name": "Debug the app",  "program": "${workspaceFolder}/main.qwe",  "args": ["--watch", "--port", "8080"],  "stopOnEntry": false}

When the program stops, all of it stops: no other request runs Quillo code until it goes on (waits already under way, for a database or the network, finish meanwhile). Pausing a server that is waiting for requests stops the next one to run.

While debugging, views are not cached (as with the option --no-view-cache), so a breakpoint in one stops each time it renders, and functions are not compiled to machine code. .qwer functions run as they are, without stops. It runs on macOS and Linux; not yet on Windows.

In the browser

A component runs in the browser too, after its first render: its event handlers and updates. With "browser": true in the launch configuration (or "edge"), VS Code opens the page in Chrome (or Edge) once the server listens, with its JavaScript debugger, in the same session: a breakpoint on a line of a .qwe file stops on the server and in the browser, wherever that line runs. "url" names the page to open (by default, the address the server listens on).

This works because in development (--watch, or under the debugger) a page's bundle is a small loader for files of their own: the runtime (quillo.js, as it is written), and each component, view and function at /_quillo/src/<hash>/<file>.qwe/<Name>.js, with a source map from its lines to the .qwe file's. The browser's developer tools show them by those names, with the .qwe source beside them, and a variable a statement declares carries its own name (count, which the compiled code calls $count_1); a component's state is a signal, its value in .v. Values are JavaScript's: the debug console takes JavaScript, not Quillo. In production a page's bundle is one minified file, as before. In both, the runtime keeps its names to itself: none of its functions is the page's, so a page's own function report() or a widget's function run() and Quillo's leave each other alone (FEEDBACK42.md, Q-481).

For developer tools

(since 0.7) A browser extension or a test can follow what the runtime does, without the debugger. It asks by setting data-quillo-devtools="1" on <html> before the runtime starts (a content script at document_start, or the page itself); a page that does not ask does nothing more. The runtime then says what happens as quillo:devtools events on document, each detail a JSON string, with values written as the wire writes them ({"$dec": "12.50"}, {"$int": "..."}, {"$quillo_err": ...}):

document.documentElement.dataset.quilloDevtools = "1";document.addEventListener("quillo:devtools", (e) => console.log(JSON.parse(e.detail)));// {"type":"component","id":1,"name":"Panel","parent":null,"props":{},"state":{"count":0}}// {"type":"state","id":1,"name":"count","value":1}
typeWhenCarries
hellothe runtime startsprotocol (1), version, dev (under --watch), base
componentone is taken up or made in the pageid, name, parent (the id of the one it is in), props, state
statea state is set to another valueid, name, value
gonea component leaves the pageid
erroran error is reportedmessage, place (the .qwe file and line, under --watch), id when known
remadea component is made again, not taken upname; under --watch, wrote (what the page had) and makes
calla server fn answersname, args, ms, ok, failed (it did not reach the server), value, live, idempotent
livea live change comesseq, topics, loads (the components whose live states load again)
channela message goes or comes, over WebTransport or a WebSocketdir (in, out), channel, value; or joined (true, false)

A tool asks with a quillo:devtools-ask event, its detail a JSON string { "id": ..., "op": ... }, and is answered {"type": "answer", "id": ..., "ok": ...}, with error when not:

op
listeach component now, as a component message
where, targetmarks the component's element with data-quillo-devtools-id (the mark moves with the next where)
set, target, name, valuesets a state; under --watch only. The JSON is read as the server reads it: 2.0 is a float, an int past 2^53 exact, a map's keys in the order written

Reading is open in production, as the page can be read with the browser's debugger anyway; setting is not (FEEDBACK42.md, Part C).

Quillo's only names on window are quillo (the server fns, above) and __quilloErrors when a page made it. What its own scripts hand each other is under one key, window[Symbol.for("quillo")]: not an interface, but a test may wait on its booted before driving a page (Q-486).

Other editors

Any editor with a Debug Adapter Protocol client (Neovim's nvim-dap, Helix, Emacs's dape, Zed) can use quillo dap as an adapter it starts itself: the command quillo, the argument dap, messages on stdin and stdout. A launch request takes:

Argument
programthe .qwe file to run (required)
argsits quillo run options (["--port", "8080"])
cwdthe folder to run in
stopOnEntrystop at the first statement
noDebugrun without stopping at breakpoints

attach is not supported yet: the debugger starts the program itself (with --no-view-cache, unless noDebug). browser and url are the VS Code extension's; elsewhere, open the page in a browser yourself. What the adapter offers: breakpoints with conditions and hit counts, logpoints, stepping, evaluation for hovers, the errors exception filter (on by default), and terminate.

Logs and metrics

Each request is logged on stderr as GET /path?query -> 200 (0.4ms): the path with its query string. Started with --log json, quillo run main.qwe --log json, the line is a JSON object instead, for log collectors: time (RFC 3339, UTC, with milliseconds), method, path (with the query string), status, ms, ip (the TCP peer), tenant when the request had one, and rid (the request's id, also sent back as x-request-id):

{"time":"2026-11-02T14:59:16.713Z","method":"GET","path":"/orders/1","status":200,"ms":0.07,"ip":"127.0.0.1","rid":"e60956f5b6f86d27"}

--log off turns the request log off; errors are still printed. Lines are buffered and written each turn of the event loop, or at 32 KiB. Not logged: /_quillo/health, /_quillo/metrics, Chrome's /.well-known/appspecific/com.chrome.devtools.json, UDP packets, channel messages. The launch token of a phone app built the old way, on 127.0.0.1, never appears in it.

An error's text can hold what a visitor sent, so in a text line its control characters and line breaks are written as escapes (\n, \x1b): one request is one line, and no one can write a line of their own into the log or colour a terminal. A malformed request line is written escaped too, and under --log json it is an object of its own, {"time", "status", "error", "line"}. Quillo's own notices (listening on, warnings, stopping) are text lines on stderr in both modes.

Started with --metrics, GET /_quillo/metrics answers in Prometheus's text format:

quillo run main.qwe --metrics --log jsoncurl -s localhost:8080/_quillo/metrics | grep quillo_requests_total# quillo_requests_total{status="2xx"} 2

What it reports:

Metric
quillo_requests_totalrequests answered, by status class
quillo_request_duration_secondsa histogram (1 ms, 2.5, 5, 10, 25, 50, 100, 250 ms, 1 s, 5 s)
quillo_requests_in_progress, quillo_connectionsnow
quillo_prefetches_total{result}pages asked for ahead of a click: answered, refused, used
quillo_later_total{result}tries of calls made later: delivered, again, failed (given up on)
quillo_live_pages, quillo_live_refused_totalpages waiting for live data, and those refused a place
quillo_live_wakes_totalpages woken by a change to what they follow
quillo_live_runs_total{outcome}loaders asked for: ran, or shared a run another page made
quillo_live_answers_total{kind}answers: same, a patch, or the whole value
quillo_uptime_secondssince the server started
quillo_bundlessets of components whose bundle is kept (see Components)
quillo_udp_received_total, quillo_udp_dropped_totalpackets (see UDP)
quillo_channel_pages, quillo_channel_dropped_totalpages on channels, messages dropped (see Channels)

It is off by default, since anyone who can reach the server could read it.

The policy

with limits what a function may do, from inside the program. A policy limits what the whole program may do, from outside it (since 0.7): the host's, for every program on a server, and the site owner's, for one deploy. Policies are small TOML files, read as the program starts, by quillo run, quillo fcgi and quillo check:

  1. /etc/quillo/policy.toml, the host's, whenever it is there (one that is there and cannot be read stops the program);
  2. each --policy FILE, in order;
  3. quillo.policy beside the program's main file.

Each file only narrows what the ones before it allow: a list is a condition every file must meet, so a later file cannot give back what an earlier one took away. A file that says something a policy does not say, or a word that is not an effect, stops the program with exit 2, naming the file and the line (quillo.policy:2: disk is not an effect). What one refuses names the file that refused it.

# /etc/quillo/policy.toml: what any program on this server may do[plugins]build = false                     # never run cargo: prebuilt libraries onlyallow = ["csv@1", "qr", "markdown"][effects]allow = ["db", "http", "fs", "time", "random", "env"]   # no net[http]private = false                   # no loopback, private or link-local addresses[env]names = ["QUILLO_SECRET", "DATABASE_URL", "APP_NAME"][limits]workers = 8max_compute = "10s"max_body = "10MB"
# quillo.policy, beside main.qwe: this program narrows it further[http]hosts = ["api.stripe.com", "*.mapbox.com"]
Section
[plugins] buildfalse: never build a Rust file, as --no-build (see Rust)
[plugins] allowthe plugins a program may use, by file name: "qr" at any version, "csv@1" or "csv@1.4.2" by the file's //! version: line. A plugin not listed is refused, by quillo check too, before anything is built. It names plugins; it does not prove their code
[effects] allowthe effects anything in the program may use, as a with on its top level, routes, jobs and functions alike; a Rust file with no //! effects: line is refused anywhere under it
[http] hoststhe hosts http.* may reach: api.stripe.com, *.mapbox.com (its subdomains, not itself), *. Each redirect is judged before it is followed
[http] privatefalse: http.* reaches nothing on this machine or its networks (loopback, private, link-local and the cloud's metadata at 169.254.169.254, carrier-grade NAT, IPv6's unique-local), as the name resolves when it connects
[env] namesthe variables env() may read; any other reads as not set, so env("HOME") ?? "none" gives "none"
[limits] workers, max_compute, max_bodythe most --workers, --max-compute and --max-body may be: a smaller value given on the command line stays ("10s", "500ms", "2m"; "10MB", "512KB" or bytes). Each takes what the option takes: 0 is refused (p.toml:2: max_body is a size larger than 0), as the option refuses it

Under the two files above, with APP_NAME=Shop:

route GET / = `${env("APP_NAME") ?? "shop"} on ${env("HOME") ?? "no HOME"}`   // Shop on no HOMEroute GET /charge = http.get("https://api.stripe.com/v1/charges").status       // reaches Striperoute GET /other = str(http.get("https://example.com/").error)// http: example.com is not among the hosts quillo.policy allows: api.stripe.com, *.mapbox.com

quillo check --policy host.toml main.qwe says, before a deploy, what the program would be refused, as errors (see Checking):

quillo: policy host.toml + quillo.policy (effects: db, http, fs, env, time, random; no builds; no private addresses)main.qwe:6: error: `udp.listen` needs the net effect, which host.toml does not allow

Quillo on a shared server (cPanel)

(since 0.7) A shared hosting account (cPanel, often on CloudLinux, with Apache or LiteSpeed in front) gives a folder, a web server the host runs, and no root. It differs from a server of one's own in three ways that matter to Quillo:

The binary. The Linux releases from 0.7.0 on are linked against glibc 2.17, so they start on CentOS 7, CloudLinux 7 and 8, AlmaLinux 8 and newer (0.6.0's need glibc 2.35). They are glibc builds, not static ones, as a plugin is a library they load.

FastCGI with mod_fcgid, where the account has it (it is in cPanel's EasyApache 4). quillo fcgi main.qwe answers FastCGI on the socket the web server gives it on file descriptor 0, and Apache starts it, keeps it up and stops it. In the site's folder:

#!/bin/sh# app.fcgi, made executable with chmod 755cd /home/u/app && exec /home/u/bin/quillo fcgi main.qwe --no-build --workers 8
# .htaccessOptions +ExecCGIAddHandler fcgid-script .fcgiRewriteEngine OnRewriteCond %{REQUEST_FILENAME} !-fRewriteRule ^ app.fcgi [QSA,L]

.htaccess and app.fcgi go in public_html (or the domain's folder); the program itself, its .env and its database stay outside it, in /home/u/app, where Apache does not serve them.

A Unix socket behind mod_proxy, where the host sets it up (a VPS with WHM, or a host that offers Quillo): quillo run main.qwe --listen unix:app.sock --trust-proxy 127.0.0.1, kept running by the host, and Apache's

ProxyPass "/" "unix:/home/u/app/app.sock|http://localhost/" upgrade=websocketProxyPassReverse "/" "http://localhost/"

(Apache 2.4.47 or later for upgrade=websocket; ProxyPass is the host's configuration, not .htaccess's). The socket is made with mode 0600, for the account's own processes: give --socket-mode 660 when the web server reaches it through its group. A socket left by a server that did not stop cleanly is taken over; one another server answers on is not (a server is listening there). Every visitor through the socket is 127.0.0.1 to the program, which the server says at start until --trust-proxy 127.0.0.1 is given; with it, request.ip is the visitor's, from the X-Forwarded-For Apache adds. Jobs reach the program through the socket.

Plugins, prebuilt. The account has no Rust, so build the program's .qwer files elsewhere with quillo build main.qwe, on Linux on the same processor, and on a system no newer than the server's (a plugin linked against a newer glibc does not load on an older one: build in a CentOS 7 or AlmaLinux 8 container). Upload .quillo/ with the program: .quillo/native/ and .quillo/plugins.lock. Run with --no-build, so that nothing is ever built on the server, and a library that is not the one built is refused rather than built again. A new quillo whose glue changed (the libraries' names carry its version) loads none of the old libraries: run quillo build main.qwe with it as part of the deploy that brings it, and upload .quillo/ again; under --no-build the server says which file was built by an older Quillo.

A policy. The host's /etc/quillo/policy.toml says what any program may do there; quillo.policy beside the program narrows it for the site (see The policy). quillo check --policy policy.toml main.qwe, run before uploading, says what would be refused.

Threads and ulimit -u. A Quillo server may use --workers + 9 threads (73 with the default 64), and on Linux the account's process limit counts them, beside PHP, cron and SSH. Give --workers 8 or so (a host's policy can cap it with [limits] workers). At start a server says when ulimit -u is below what it may use, and a thread the system refuses is said once (see Concurrency and reloading).

The rest of the account.

Thread
16Useful? Like it, or add to its thread below.

Thread 0 posts

On Concurrency and reloading: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.

    Edit this page on GitHub · made from the user guide · questions on Discord or r/quillo