diff --git a/.github/workflows/release-cli.yml b/.github/workflows/release-cli.yml index 71c378e3..9b150a1e 100644 --- a/.github/workflows/release-cli.yml +++ b/.github/workflows/release-cli.yml @@ -111,10 +111,12 @@ jobs: cli/dist/conduit-linux-arm64 cli/dist/conduit-linux-armv7 cli/dist/conduit-linux-armv6 + cli/dist/conduit-linux-386 cli/dist/conduit-monitor-linux-amd64 cli/dist/conduit-monitor-linux-arm64 cli/dist/conduit-monitor-linux-armv7 cli/dist/conduit-monitor-linux-armv6 + cli/dist/conduit-monitor-linux-386 cli/dist/checksums.txt - name: Create Release @@ -130,6 +132,7 @@ jobs: cli/dist/conduit-linux-arm64 cli/dist/conduit-linux-armv7 cli/dist/conduit-linux-armv6 + cli/dist/conduit-linux-386 cli/dist/conduit-darwin-amd64 cli/dist/conduit-darwin-arm64 cli/dist/conduit-windows-amd64.exe diff --git a/cli/Makefile b/cli/Makefile index 54168f23..0b2cc3cb 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -24,15 +24,16 @@ PSIPHON_REPO := https://github.com/Psiphon-Labs/psiphon-tunnel-core.git # $(3) = GOOS # $(4) = GOARCH # $(5) = GOARM (optional) +# $(6) = extra env vars (optional, e.g. GO386=softfloat) define GO_BUILD @BUILDDATE=$$(date +%Y-%m-%dT%H:%M:%S%z) && \ BUILDREPO=$$(cd psiphon-tunnel-core && git config --get remote.origin.url) && \ BUILDREV=$$(cd psiphon-tunnel-core && git rev-parse --short HEAD) && \ GOVERSION="$$($(GO) version | sed 's/go version //')" && \ ALL_TAGS="$(BASE_TAGS)" && \ - if [ -n "$(2)" ]; then ALL_TAGS="$$ALL_TAGS,$(2)"; fi && \ + if [ -n "$(2)" ]; then ALL_TAGS="$$ALL_TAGS $(2)"; fi && \ echo "Building $(3)/$(4) -> $(1) with tags: $$ALL_TAGS" && \ - CGO_ENABLED=0 GOOS=$(3) GOARCH=$(4) $(if $(5),GOARM=$(5)) $(GO) build \ + CGO_ENABLED=0 GOOS=$(3) GOARCH=$(4) $(if $(5),GOARM=$(5)) $(if $(6),$(6)) $(GO) build \ -tags "$$ALL_TAGS" \ -ldflags "\ -s -w \ @@ -50,9 +51,10 @@ endef # $(2) = GOOS # $(3) = GOARCH # $(4) = GOARM (optional) +# $(5) = extra env vars (optional, e.g. GO386=softfloat) define GO_BUILD_MONITOR @echo "Building monitor $(2)/$(3) -> $(1)" - @CGO_ENABLED=0 GOOS=$(2) GOARCH=$(3) $(if $(4),GOARM=$(4)) $(GO) build \ + @CGO_ENABLED=0 GOOS=$(2) GOARCH=$(3) $(if $(4),GOARM=$(4)) $(if $(5),$(5)) $(GO) build \ -ldflags "-s -w" \ -o $(1) ./scripts/monitor endef @@ -133,6 +135,10 @@ build-linux-armv6: check-go check-setup $(call GO_BUILD,dist/conduit-linux-armv6,,linux,arm,6) $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-armv6,linux,arm,6) +build-linux-386: check-go check-setup + $(call GO_BUILD,dist/conduit-linux-386,netgo,linux,386,,GO386=softfloat) + $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-386,linux,386,,GO386=softfloat) + build-darwin: check-go check-setup $(call GO_BUILD,dist/conduit-darwin-amd64,,darwin,amd64) $(call GO_BUILD_MONITOR,dist/conduit-monitor-darwin-amd64,darwin,amd64) @@ -150,7 +156,7 @@ build-freebsd: check-go check-setup $(call GO_BUILD_MONITOR,dist/conduit-monitor-freebsd-amd64,freebsd,amd64) # Build for all platforms -build-all: check-go check-setup build-linux build-linux-arm build-linux-armv7 build-linux-armv6 build-darwin build-darwin-arm build-windows build-freebsd +build-all: check-go check-setup build-linux build-linux-arm build-linux-armv7 build-linux-armv6 build-linux-386 build-darwin build-darwin-arm build-windows build-freebsd # Build for all platforms with embedded config build-all-embedded: check-go check-setup check-psiphon-config @@ -160,6 +166,7 @@ build-all-embedded: check-go check-setup check-psiphon-config $(call GO_BUILD,dist/conduit-linux-arm64,$(EMBED_TAG),linux,arm64) $(call GO_BUILD,dist/conduit-linux-armv7,$(EMBED_TAG),linux,arm,7) $(call GO_BUILD,dist/conduit-linux-armv6,$(EMBED_TAG),linux,arm,6) + $(call GO_BUILD,dist/conduit-linux-386,$(EMBED_TAG) netgo,linux,386,,GO386=softfloat) $(call GO_BUILD,dist/conduit-darwin-amd64,$(EMBED_TAG),darwin,amd64) $(call GO_BUILD,dist/conduit-darwin-arm64,$(EMBED_TAG),darwin,arm64) $(call GO_BUILD,dist/conduit-windows-amd64.exe,$(EMBED_TAG),windows,amd64) @@ -170,6 +177,7 @@ build-all-embedded: check-go check-setup check-psiphon-config $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-arm64,linux,arm64) $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-armv7,linux,arm,7) $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-armv6,linux,arm,6) + $(call GO_BUILD_MONITOR,dist/conduit-monitor-linux-386,linux,386,,GO386=softfloat) $(call GO_BUILD_MONITOR,dist/conduit-monitor-darwin-amd64,darwin,amd64) $(call GO_BUILD_MONITOR,dist/conduit-monitor-darwin-arm64,darwin,arm64) $(call GO_BUILD_MONITOR,dist/conduit-monitor-windows-amd64.exe,windows,amd64) diff --git a/cli/cmd/ish_linux_386.go b/cli/cmd/ish_linux_386.go new file mode 100644 index 00000000..925c0094 --- /dev/null +++ b/cli/cmd/ish_linux_386.go @@ -0,0 +1,13 @@ +//go:build linux && 386 + +package cmd + +import "runtime" + +func init() { + // iSH's %gs TLS emulation is unreliable in signal handler context. With + // multiple Ps, Go sends SIGURG to preempt goroutines across OS threads, + // which triggers a "bad g in signal handler" crash. GOMAXPROCS=1 keeps + // all goroutines on a single P, preventing cross-thread SIGURG delivery. + runtime.GOMAXPROCS(1) +} diff --git a/src/hosted/sessionQueries.ts b/src/hosted/sessionQueries.ts index d12dec81..e5f277a9 100644 --- a/src/hosted/sessionQueries.ts +++ b/src/hosted/sessionQueries.ts @@ -92,21 +92,48 @@ export async function ensureHostedSession( return refreshHostedSession(queryClient, input); } +// Deduplicates concurrent refresh calls for the same queryClient+baseUrl so +// that parallel queries racing on an expiring token only trigger one network +// request. WeakMap keying avoids cross-test pollution. +const inflightRefreshes = new WeakMap< + QueryClient, + Map> +>(); + export async function refreshHostedSession( queryClient: QueryClient, input: HostedSessionDependencies, ): Promise { - try { - const refreshed = await input.sessionClient.refresh(); - await setHostedSessionState(queryClient, input, refreshed); - return refreshed; - } catch (error) { - if (error instanceof HostedApiRequestError && error.status === 401) { - await clearHostedSessionState(queryClient, input); - throw new Error("Hosted session expired; please sign in again"); - } - throw error; + let byUrl = inflightRefreshes.get(queryClient); + if (!byUrl) { + byUrl = new Map(); + inflightRefreshes.set(queryClient, byUrl); } + + const existing = byUrl.get(input.baseUrl); + if (existing) return existing; + + const promise = (async () => { + try { + const refreshed = await input.sessionClient.refresh(); + await setHostedSessionState(queryClient, input, refreshed); + return refreshed; + } catch (error) { + if ( + error instanceof HostedApiRequestError && + error.status === 401 + ) { + await clearHostedSessionState(queryClient, input); + throw new Error("Hosted session expired; please sign in again"); + } + throw error; + } finally { + byUrl.delete(input.baseUrl); + } + })(); + + byUrl.set(input.baseUrl, promise); + return promise; } export async function withHostedSessionRecovery(