Back to list

Development Update — July 15

A quiet, sharp-edged day: a pair of concurrency races that had been hiding inside the skynet server and the cxo Filler got pulled out of a larger exchange-engine branch and fixed on their own, so the reliability work wouldn’t have to wait on the bigger review. Both bugs were the same shape — a goroutine registering work on a WaitGroup at the exact moment another goroutine was tearing the whole thing down — and both are the kind of race that stays invisible until a CI runner or a production visor happens to interleave the two just so. In the skycoin monorepo the day was all about making a TinyGo build of the node real and shippable — porting the web wallet off gin, embedding the wasm that matches the toolchain, colored help without text/template, and a release job that actually produces a TinyGo archive.

Skywire: Add-after-Wait Races and CI Hardening

3494 fix(concurrency): Add-after-Wait races in skynet.Server + cxo Filler (+ CI test hardening) extracts the general reliability fixes from the exchange PR (#3434) so they can land and be reviewed independently of that larger design discussion. Two races, one shape. In pkg/skynet/server.go, Serve() calls activeConn.Add(1) for each freshly Accepted connection — but that Add could run concurrently with Close()’s close(closeCh) and activeConn.Wait(), which is an Add-after-Wait on the WaitGroup: a data race, a potential panic, and a connection that escapes graceful shutdown entirely. The fix registers each connection under mu, gated on a closed flag that Close sets together with close(closeCh) and listener.Close() under that same lock — so Serve provably stops registering before Wait runs, while Wait itself moves outside the lock so handlers can RLock as they serve. The cxo Filler had the identical bug: Filler.Go()’s await.Add(1) could escape Close()’s close(closeq) and await.Wait(), letting a Split goroutine touch the filler after Close had already returned — a use-after-teardown. That Add is now serialized under f.mx against the closeq close, releasing the acquired limit slot and bailing if the filler is already closing. Both packages pass go test -race, and a new server_overlap_test.go deliberately exercises the Serve/Close overlap. Riding along is a bit of CI hygiene: pkg/skyudpbridge and the ws_native transport had 2–3 second read/shutdown deadlines that were too tight for slow CI runners and flaked intermittently; those are bumped to 10 seconds, with no production change.

Skycoin: Building Skycoin Under TinyGo

The day’s skycoin work was all about making a TinyGo build of the node real and shippable. The thin-client web wallet was built on gin, which unconditionally pulls in quic-go/http3 (QUIC TLS APIs TinyGo lacks) and ugorji/go/codec (indexes a cache by reflect.Kind, whose numbering differs under TinyGo and crashed at startup), so the web command had been stubbed out under TinyGo; porting it to standard-library net/http behind a small webCtx shim that mirrors the handful of *gin.Context methods the handlers used — routing with exact and /-terminated subtree patterns only, since TinyGo’s ServeMux doesn’t implement Go 1.22 wildcard patterns, with the coin index parsed from the URL in parseCoinPath — collapses it to one code path under both toolchains and removes the root_tinygo.go stub, enabling pprof everywhere. On top of that, the wallet now embeds the wasm matching its build toolchain by build tag (standard-go embeds wasm-go, TinyGo embeds wasm-tinygo, each paired with its matching wasm_exec.js) instead of shipping the TinyGo wasm unconditionally, and the CLI’s colored help works under TinyGo by switching to the 0magnet/coloredcobra fork, whose //go:build tinygo renderer reproduces cobra’s layout and colors without text/template (which invokes template methods through reflect.Value.Call, unimplemented in TinyGo and previously panicking) — letting the plain-help help_tinygo.go override be deleted, while go mod tidy drops the now-unused gin / quic-go / ugorji tree from vendor.

The release CI then learned to actually produce a TinyGo build: a new linux-tinygo job installs LLVM 22, builds the 0magnet/tinygo fork (v0.42.0-skycoin.1), and tinygo build -gc=precise -tags "safe netgo" the repo root into a distinct skycoin-<tag>-linux-amd64-tinygo.tar.gz. The workflow was reworked around it — the main binary now builds from the repo root (pure Go, CGO disabled, cross-compiling every arch with plain GOOS/GOARCH), the cgo hardware-wallet utilities split into their own musl-static skyhw archive, and every arch builds from an actions/checkout of the tagged ref instead of go install @tag so forks can cut alpha releases too — plus a round of fixes from the first alpha run: permissions: contents: write so the token can create the release, fetching TinyGo’s compiler-rt builtins into the fork checkout (a source checkout lacks them), and fail-fast: false on the Linux matrices.