Unreleased [Not released yet]
Part 6 of 11. Previous part
Fixed (464)
- Linux: a stop signal that comes between the bind and the start of the workers stops the server (exit 0). Before, it killed the server. Then the rings of the workers kept the port listening for some milliseconds after the exit and reset new connections, also those of the next server on that port.
- io_uring without a buffer ring (Ubuntu 24.04's 6.8): after the kernel refuses a receive buffer, or
io_uring_enterfails withENOMEMorEBADR, eachPROVIDE_BUFFERScarries one buffer. The kernel reports a run of buffers that it takes in part as a success, with no completion. Before, the buffers that it did not take went missing. After at least 10 s with no refusal and no memory error, runs return. - io_uring without a buffer ring: the kernel can hold no receive buffer of a worker while the pool cannot grow. When none returns for a second and no
PROVIDE_BUFFERSwaits in the queue, the worker counts every buffer as lost. A receive out of buffers then ends its connection. A buffer that returns after that giveslostits old value. Before, it rearmed in each turn, and the worker used a full CPU. - io_uring: the worker reads the drop count of the ring (
cq_off.overflow) once a turn. When it changes, the worker ends the connections that wait on the ring, as it does forEBADR. The kernel counts a dropped completion there and sets no overflow flag.io_uring_enterof the worker asks for no completion, so it may not returnEBADRfor it. Before, the worker may have left those connections open. - io_uring: entries that wait behind a full submission queue go into the queue in the turn that makes room. Before, the driver could sleep with them waiting, and a rearm, a close or a cancel waited for other traffic.
- io_uring: the receive buffers grow also after 2^31 submissions since the last growth. Before, a signed compare turned negative, and they did not grow for the next 2^31 submissions.
- io_uring: a ring that drops keeps the addresses of its receive buffers, with no access. The kernel can cancel the receives of a closed ring later (in
io_ring_exit_work), so a late receive fails withEFAULT. Before, it could write into a new mapping at the same address. - io_uring: the start proof does not take the whole large request while the cancel of its pause is on its way. Before, the proof could fail on a good ring, and the server used epoll.
- io_uring: a failed
io_uring_enterthat the driver did not handle (for exampleEBADForEINVAL) no longer stops the server. The worker ends the connections that wait on its ring, says so once on stderr, and tries again after 50 ms, with a longer wait each time, up to 1 s. After 30 failed turns in a row the worker stops accepting (it shuts its listener down), so the kernel may send new connections to the other workers. When every worker stops, the process exits so that the supervisor restarts it. The log line of a fallback start names the first way that failed:(buffer ring with DEFER_TASKRUN: ...). A buffer id wraps inside the buffers that the ring maps. wisp checklint fixes: - A{...}group with a quote that never closes no longer makes the scan of a template,<style>or<script>slow. The time was quadratic in the text. It is linear now. - The accessibility check keeps at most 64 open<a>,<button>,<label>and<video>elements. A file of unclosed tags took quadratic time. -wisp check --fixno longer deletesrole="link"from an element that has a spread attribute. The spread may set thehref. - Animportthat two<script>blocks repeat is no longer a use of the name. The file is read once for all its scripts. - A.wispfile whose Rust block follows a blank line no longer has that block linted as CSS or JavaScript. - A CSS block nested deeper than 256 levels no longer closes the wrong block. Its declarations are not compared with the declarations of a block around it. -wisp check --fixcreates its temporary file with a name of its own (process id and a counter). It deletes no file of yours. It fails when it cannot set the mode of the file. - The route type files in.wisp/route-typesare not written through a symbolic link. -wisp check --fix --dry-runwith--rust,--types,--targetor--explain-importsis an error. Before, it skipped them without a message. - A nested array in the[package]table ofCargo.tomlno longer hides the package name. - The seo lints read each layout once per run, not once per page. -wisp helpfits 100 columns again: the rows ofwisp route add,wisp build --analyze,wisp build --allow-dead-links,wisp check --rustandwisp check --fixare shorter.- A
{...}group with a quote that never closes no longer makes the next groups of the same scan end at a}inside a string. The scan reads quotes again for each group, for up to 8 such groups. After that it ignores quotes, to stay linear. - A
\"inside a string ofCargo.tomlno longer hides the next table header from the package name check. A string with"""or'''over several lines is still not read. - A template with many
<script>,<style>,<textarea>or<title>tags that have no close tag no longer takes quadratic time to open. The scan of such tags is linear now. - The code frame of the dev error page reads at most 1 MiB of a source file, also when the file grows after the size check.
- A build plugin command replaces
{in}and{out}in one pass. Before, an input path that held the text{out}got the temporary output path. A failed command leaves no{out}file, and a plugin that the app no longer names leaves thesrcorstaticfolder. - A build plugin command starts when the system refuses the group of its helper process. Before, the build failed with a permission error that named the plugin.
- Content collections: a whole-number
datefield sorts by value, so 10 comes before 9 in the newest-first order. Before, the order was by text. - A
<meta name=robotsx content="noindex">tag no longer keeps a page out of the sitemap. At\n('key')call in a script is translated too. wisp build --analyzehas a full sentence inwisp help.wisp check --fixfails for a file when the file system cannot set its mode. Theclipage says so.wisp lspno longer takes quadratic time on a text with many open tags such as<Card {that never close. Linked editing, rename of a prop and the selection ranges read at most the text length plus 512 KB of tag text in one request. A tag past that budget counts as not whole, so a rename refuses (as for any broken tag).- A
CACHEstore that is full of live entries now makes room. Before, it kept nothing new until an entry died, so a flood of new URLs stopped all new entries. Now a full store deletes the dead entries first, then the live entries nearest their end time (one eighth of the 8 MB limit at a time). A flood of new URLs can push out pages that clients asked for. It costs renders, never memory. - A route with more than one optional or rest segment gets one path for each form in the OpenAPI document:
/[[a]]/x/[[b]]has four paths. Before, only the full path and the path without all of them were listed. Only the first 8 such segments of a route give forms. wisp buildreads thestatic/folder of each layer when it checks the files of the app. A prerender return type is checked by whole type names, soVec<ImageMeta>is JSON andOption<Svg>is not. An uppercase close tag such as</STYLE>ends a block in an island scan. A dependency key such aschrono.workspace = truenameschrono.- An intercepting folder with a name that a browser would percent-encode, such as
(.)café, now gets the same error as a plaincaféfolder. Before, the error said that the folder intercepts a route that is not a page. - The
///ofmin_lennow says that the browser can pass a text that the server refuses. The browser counts UTF-16 units and the server counts characters. wisp-buildhas tests for the order of routes:/[[lang]]/hellotakes/hellobefore/[slug], and a rest route comes last. The order is in the routing docs andllms/AGENTS.md.- The tree-sitter query (Neovim, Helix, Zed) marks an
on*attribute as JavaScript for every event handler of the HTML standard, such asonpaste,onfocusinandonplay. Before, a closed list left some plain.once,onlyandonestay plain. In VS Code, a bare value of a--propcan hold a/, as in--w=url(a/b). Only/>ends it. wisp ui add button Buttonwrites the component once. Before, it told you that it skipped the file it had just written.- A
{:else}or{:case}in the wrong place says where the word goes. A{:case}with no pattern asks for one. - A hole that starts with a block comment, such as
${/* note */ x}, is an expression. Before, the build read it as the end of a block. wisp route addrefuses a folder that the route scanner skips (a name that ends in.bak,.tmpor.orig, or starts with a dot) and says why. Before, it wrote a route that no request reached. It writes.well-known, which the scanner serves. A Windows device name (con) and a non-ASCII letter get their own reason, and so doeswisp component add Con.wisp check --explain-importslists a file once. Before, a page with amod servercame twice.- A front matter line of a Markdown page that continues a value (a list or a
|block) says that Wisp reads onename: valueline for each field. A line with no:says so. wisp new --libwith no name asks for a library folder, not an app folder.wisp deploy initwith no host, with only--force, with an extra word, or with another subcommand says which of them is wrong, and lists the hosts.- The template parser takes linear time on big files. Before, the time for some shapes grew with the square of the file size. These shapes are many end tags with no open tag, fields in many nested forms, many snippets, and snippets in many
<template>s. Live elements (withon:and other directives) on one long line also took this time. So did live elements in many client<template>s, and many<form fields>or<form guard>. So did a---block with manyletlines that the browser reads. So didwisp lspon a file with many close holes and no open block for them. A<template each>or<template if>now nests at most 128 deep:<template> elements nested more than 128 deep (W0104). A 910 KB file of the first shape took about 32 s to check in a debug build. Now it takes 125 ms. This fixeswisp build,wisp checkand the problems thatwisp lspshows. The build also finds a component by its name in one step. Before, each tag looked through all components. It also reads the line of each trailing-slash warning in one pass of the file. The check of a prerendered page finds each component it renders in one step too. Before, its time grew with the cube of the component count. #[derive(Rest)]: a PUT of a type with#[json(skip)]fields fails with a 409 when the row changes between its read and its write. Before, the PUT wrote the old values of those fields, and the other write was lost. The OpenAPI schema of a PUT and a PATCH lists the 409.#[json(skip)]with#[validate(...)]on the same field stops the build: "a skipped field takes no#[validate(...)]". Before, Wisp did not use the rules and showed no error.#[json(...)]on the type stops the build too.- Gate
audit: a path that starts with@matches its commit date. The date line of the history walk starts with NUL now. A stale file with no known change day is late, and a ledger date after today is overdue. - Gate
stamps: a repo text stamp must name the newest release. Before, a stamp of an older release with a valid commit passed. - Gate: in CI,
WISP_SKIP_STEand a missinglakemake thesteandproofssteps fail. CI installs elan from a pinned commit ofelan-init.shand checks its sha256. The pin (ELAN_COMMIT,ELAN_SHA256inci.ymlandrelease.yml) must be set. While it is not set and the runner has nolake,ci.ymlprintsproofs: SKIPPED (elan pin is not set). The release workflow fails then. - Gate
audit-mark: a picked file with changes that are not in HEAD makes it fail. Commit the audited bytes first. - Gate
prerender: a test run that runs no test fails. Before, a name filter that matched nothing passed. - Gate: the skip log and the site copy use a new temp folder with a unique name.
create_dirfails if the name exists. - Gate
stamps: a table with|:--|alignment rows counts as a table. A<Measured>block with no</Measured>line is a problem. - Gate
site: a page that the step cannot read is a problem. The step fails when the docsCargo.tomlnames no../wisp/cratesor../Wisp/cratespath. - The TFB tables take runs of one session only. Each run file of
bench/tfb/run.shnames its session and Wisp rev.aggregate.pyandaggregate_db.pyrefuse runs of more than one session unless--allow-mixedis given, and then they do not rank a cell that mixes sessions. They list each contender with no run and no OMITTED note.aggregate_db.pyskips the old run files of an omitted contender or workload. A cell with a stalled run, or with socket errors over 0.1% of its requests, is not ranked, for every contender. Every Rust contender, Wisp too, builds with the sameRUST_BUILD_FLAGS(-C target-cpu=native, as TFB's axum and actix dockerfiles). bench/rust-fastest/run.shwrites the session and the Wisp commit in each row ofruns.tsvand refuses aruns.tsvof an older header.rank.pyrefuses rows of more than one session unless--allow-mixedis given and lists missing contenders.sc.shsubtracts a start-stop base of each contender from its syscall count, so startup and teardown are not counted per request.sc.pyreads chunked replies. A contender with no count gets a FAILED row.bench/rank/report.mjsshows a run fromresults/earlier/with no place, because it has no Wisp commit. It shows a cold start with a failed start, with its count, and does not rank it. It prints the response bytes per request of each route (oha).merge-reruns.mjsfails unless every rerun holds every contender of each route it touches, and it keeps every measured run in place of per-index medians.bench/edge/ir.mjswarms Deno with 40,000 requests, asdeno-prof.sh.ab.mjs,run.mjsandworkerd.mjsare marked dev-only: no published number comes from them.bench/site-speed/proxy.mjsstreams gzip with a flush for each chunk of the app, so an app that streams its HTML keeps its early bytes. Before, the proxy kept the full reply before it sent it. Site-speed numbers of the old proxy do not compare with new ones.bench/build/measure.mjsreads the artifact size only when every build of the session built. Before, a failed build could show the size of the output of an earlier build.- The page https://wispweb.dev/changelog/ lists the versions and the downloads. Each version of this file has its own page:
/changelog/1.0.0-pre.3/, and/changelog/unreleased/for theUnreleasedsection. A release renames theUnreleasedsection, and the nextUnreleasedsection is empty, so after a release/changelog/unreleased/shows an empty section. Before, one page held every version and was over its size budget. wisp-gate sitecopies the docs site with aCargo.tomlthat names../Wisp/cratesas well as../wisp/crates. Before, a checkout in a folder with a capitalWkept the old path, so the build failed.wisp-gate sitebuilds the reference site withSITE_URL=https://wispweb.dev, as the deploy does, sofeed.xmlandsitemap.xmlexist and their links resolve. It prints the largest page for each budget (HTML, inline CSS, CSS, JS, fonts).clean_packagereads--target=<triple>as well as--target <triple>.- The TypeScript client renames a route parameter that is a reserved word or a name its method body uses (
enum,import,String,encodeURIComponent,arguments,eval), and two parameters that give one name ([a-b],[a_b]) get a unique name each. Before, the client could be invalid TypeScript. - A parameter
r#typein a+server.rsfunction reads the request inputtype. Before, the build refused it with a wrong message. - tree-sitter grammar:
<<in<script>or<style>(a shift operator) stays raw text. uitemplates:Tooltipadds its id to thearia-describedbyof the child, it no longer replaces it.Buttonwithhrefanddisabledrenders nohrefand setsaria-disabled.Skeletonclampslinesto 1 to 50.- The compaction of a table log starts again after the thread for it failed to start. Before, the log stayed marked as compacting, so it grew with no limit. Now the log clears the mark, writes a line to the log of the app, and tries again after the size doubles.
- A release build of an app with only a binary target keeps no path of the app folder.
App::ROOTis"."in a release build, because the commands ofwisp buildrun in the app's folder. The generated file keeps the path only in a debug build.wisp buildruns onecargo rustc --binfor each binary with--remap-path-prefix=<workspace folder>=.and--remap-path-prefix=<workspace folder><separator>=for the last rustc call only. So a path in the folder is relative, also in the compiler errors.RUSTFLAGSand the cargo config apply as before, and the dependencies stay shared withcargo build --release. A build with the build cache remaps the cache folder to./target. Paths of path dependencies, of$CARGO_HOMEand of the toolchain stay in the binary. With[[bin]]orautobinsinCargo.toml, the build is plain and has no remap. An app with a library target builds plain too, and the path stays in the library.App::ROOTno longer adds the checkout path to the wasm. bench/native-rank: a failed build exits non-zero, andbenchdoes not start the old binary. A failed build, start or load inbenchwrites afailedfile, and the other contenders still run.run.sh allgoes on after a failed build and exits non-zero at the end. The report shows a row for every contender with the reason it has no rank. It refuses a pass that mixes sessions (--allow-mixedprints it with a warning). Passes keep their own number. Values within 5% share a rank, and an unranked cell counts as last in the rank sum. Memory is PSS summed over the server's processes, so a shared page counts once. With no Pss, the file says so, never 0 MB. Each run records the CPU use ofoha. A cell whereohaused 90% or more of its cores gets^. The apps commit their lockfiles, and the builds refuse to change them (FastAPI is not pinned yet). The Wisp app uses the repo-rootCargo.lock, and the build fails when a third-party version differs from it.bash selftest.shchecks the oha CPU reading and the Pss sum on Linux. No published number changes.SSR,CSR,PRERENDER,EARLY_HINTS,PRECACHEandTRAILING_SLASHin a+server.rs, andCSRFin a page, now fail the build. Before, the build ignored them.- On an edge host, a baked page no longer keeps a 304 as the answer of its path. Before, when the first request to the path sent a matching
if-none-match, the bridge kept the 304. Each later plain GET then failed (Invalid response status code 304on Node). Now the bridge keeps only a full answer. #[derive(Rest)]lists: a?sortpage no longer sorts every row. It selects the firstoffset + limitrows, then sorts only those. The sort keys of all rows go in oneString, not oneStringfor each key. Filters on one field write and parse its value once for each row, not once for each filter.#[derive(Rest)]POST: anif-none-matchwith an empty list member, such as"x",, was a 412 as if it named*. RFC 9110 allows empty list members, so it is now no match.- An edge app with no
SITE_URLhas no base for/sitemap.xml,/robots.txtand/feed.xml, so it serves none of them. SetSITE_URLon the edge host. Before, the edge took the base from theHostheader. - An offline
data-wisp-queuepost says "Saved" only after/_app/c/queue.jskeeps it. A page with such a form loads queue.js while online. When it cannot load, or sessionStorage cannot hold the post (full or blocked), the post is not kept and the offline message shows. wisp.js sends waiting posts at the start and ononline, also after a reload offline.