Add linux/386 build target for iSH on iOS#233
Open
badoriie wants to merge 2 commits into
Open
Conversation
badoriie
marked this pull request as draft
May 27, 2026 18:29
badoriie
marked this pull request as ready for review
May 27, 2026 19:34
iSH (https://ish.app/) is a Linux x86 emulator for iOS that lets volunteers run server-side software on an iPhone. This adds first-class support for running a Conduit node on iSH, opening up Conduit to people who only have an iOS device and want to help users in restricted countries where the iOS app is not yet on the App Store. Changes: - New `build-linux-386` Makefile target producing statically linked x86 32-bit binaries (conduit-linux-386, conduit-monitor-linux-386) - Built with GO386=softfloat: iSH emulates SSE2 instructions in software; softfloat makes Go emit simpler integer instructions instead, reducing emulation overhead - Built with netgo tag: uses Go's pure-Go DNS resolver, avoiding glibc DNS syscall overhead under iSH's emulation layer - GOMAXPROCS=1 set at startup (cmd/ish_linux_386.go): iSH's %gs thread-local storage emulation is unreliable in signal handler context. With multiple Ps, Go sends SIGURG to preempt goroutines across OS threads, triggering a "bad g in signal handler" crash. Keeping all goroutines on a single P prevents this. - conduit-linux-386 added to release artifact uploads and GitHub release assets in the CLI release workflow - GO_BUILD and GO_BUILD_MONITOR macros extended with an optional extra-env parameter so GO386=softfloat can be passed without duplicating build logic Tested on iPhone running iSH with Alpine Linux. The binary starts and successfully relays traffic with --max-common-clients 2. Usage on iSH: apk add curl curl -L -o conduit https://github.com/.../conduit-linux-386 chmod +x conduit ./conduit start --max-common-clients 2
Multiple background queries (useHostedAccountProfileQuery, useHostedConduitsQuery) each call withHostedSessionRecovery on mount. When the loaded session is expiring, all of them independently call sessionClient.refresh() before any one of them has written the refreshed token back to the query cache. This results in redundant parallel refresh requests. Fix by coalescing concurrent refresh calls for the same queryClient+baseUrl into a single in-flight promise, using a WeakMap<QueryClient, Map<baseUrl, Promise>> so all concurrent callers share one network request. WeakMap keying on queryClient ensures each test gets an isolated map without any teardown needed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Background
iSH is a free iOS app that runs an Alpine Linux x86 environment on iPhone using a user-mode x86 emulator. It lets volunteers run server software on a phone they already own — no VPS required.
Since the Conduit iOS app is not currently on the App Store, iSH is a practical path for iOS users to contribute as a Conduit node. I tested this end-to-end on my own iPhone and it works.
What this PR does
1. linux/386 CLI build target (
cli/)Adds
build-linux-386to the Makefile and wires it intobuild-all,build-all-embedded, and the release workflow soconduit-linux-386appears in every release.Three build-time optimisations specific to iSH:
GO386=softfloatnetgoGOMAXPROCS=1(runtime,cmd/ish_linux_386.go)%gsthread-local storage emulation is unreliable in signal handler context. With multiple Ps, Go sends SIGURG to preempt goroutines across OS threads, which triggers afatal: bad g in signal handlercrash on startup. Limiting to one P keeps all goroutines on a single scheduler, preventing the cross-thread SIGURG delivery that causes the crash.The binary is statically linked (
CGO_ENABLED=0) so it runs on iSH's Alpine/musl userland without any extra dependencies or setup beyondchmod +x.2. Concurrent session refresh race condition fix (
src/hosted/sessionQueries.ts)Unrelated to iSH, but found while working in this area.
When a session is about to expire,
useHostedAccountProfileQueryanduseHostedConduitsQueryboth mount and independently callwithHostedSessionRecovery → ensureHostedSession → refreshHostedSession. Because they run concurrently before either has written the refreshed token back to the cache, they both see the expiring token and both callsessionClient.refresh()— producing redundant parallel refresh requests.Fixed by coalescing concurrent calls per
queryClient+baseUrlinto a single in-flight promise using aWeakMap<QueryClient, Map<string, Promise<HostedSession>>>. All concurrent callers share the same request.WeakMapkeying onqueryClientgives each test instance an isolated map automatically, with no teardown needed.Testing
iSH (iPhone):
Confirmed working — binary starts, announces, and relays traffic.
Session refresh fix:
npm run testAll 483 tests pass. The previously failing test (
refreshes expiring sessions via session client) now correctly expectssessionClient.refreshto be called once.