Skip to content

Using water shared runtime#281

Merged
WendelHime merged 5 commits into
mainfrom
lanternd/water-shared-runtime
Jun 30, 2026
Merged

Using water shared runtime#281
WendelHime merged 5 commits into
mainfrom
lanternd/water-shared-runtime

Conversation

@WendelHime

@WendelHime WendelHime commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

This PR use the water shared runtime dialer for the outbound for reducing the high memory consumption and cpu usage

Summary by CodeRabbit

  • New Features

    • Added a new option to run the WATER transport with an optimizing compiler for improved performance on supported platforms.
  • Bug Fixes

    • Updated the WATER transport to use a shared runtime, improving connection handling and reducing setup overhead.
    • Improved cleanup during shutdown and connection setup to help prevent resource leaks and hanging connections.
  • Chores

    • Updated the bundled WATER dependency to a newer version.

WendelHime and others added 3 commits June 17, 2026 12:16
Build a single waterv1.SharedDialer per Outbound in loadConfig (compiles the
WASM module once) and reuse it for every dial; each dial only instantiates a
fresh guest instance, eliminating the per-dial CompileModule that leaked the
interpreter's compiled functions (unbounded, multi-GB) and re-decoded the binary
every connection. newDialer becomes dialConfig (returns the per-dial config);
dialTCP dials via the shared dialer; Close closes it.

Also: optional compiler engine via UseCompiler (defaults to interpreter for the
NetworkExtension jetsam limit); shallow-copy the per-dial config; close the conn
on stream end since water only auto-closes on worker exit.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@WendelHime WendelHime changed the title Lanternd/water shared runtime Using water shared runtime Jun 22, 2026
@WendelHime WendelHime marked this pull request as ready for review June 22, 2026 14:12
Copilot AI review requested due to automatic review settings June 22, 2026 14:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR switches the WATER outbound implementation to use the v1 shared runtime/dialer so the WASM module is compiled once and reused across dials, and adds an option to enable wazero’s optimizing compiler on hosts where it’s safe.

Changes:

  • Build and retain a waterv1.SharedDialer during background config load, and use it for all subsequent TCP dials.
  • Add use_compiler to WATER outbound options to toggle compiler vs interpreter runtime configuration.
  • Bump the github.com/getlantern/water replacement to a newer revision.

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 1 comment.

File Description
protocol/water/outbound.go Introduces shared WATER runtime/dialer lifecycle and updates per-dial config + dialing logic.
option/water.go Adds UseCompiler option for choosing compiler vs interpreter.
go.mod Updates the github.com/refraction-networking/water replacement target revision.
go.sum Updates module checksums for the bumped water revision.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread protocol/water/outbound.go
The detached dial (context.WithoutCancel) ignores caller cancellation,
so an already-canceled caller paid for a full dial before the post-dial
check noticed. Short-circuit on ctx.Err() up front. When cancellation is
detected after a successful dial, dispatch rawConn.Close() to a goroutine
since WATER's Close can block and would otherwise stall the cancellation
path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Updates the getlantern/water module pin in go.mod, adds a UseCompiler boolean field to WATEROutboundOptions, and refactors Outbound to construct a single waterv1.SharedDialer at load time (with compiler/interpreter mode selection), replacing per-dial runtime creation. Dial logic now uses per-dial shallow-copied configs with detached contexts, and Close tears down the shared dialer.

Changes

WATER Shared Dialer Refactor

Layer / File(s) Summary
UseCompiler option and dependency pin
option/water.go, go.mod
Adds UseCompiler bool (use_compiler) field to WATEROutboundOptions and bumps the replace directive for refraction-networking/water to a newer getlantern/water commit.
SharedDialer construction and struct wiring
protocol/water/outbound.go
Changes the waterv1 import from blank to named, adds sharedDialer *waterv1.SharedDialer to Outbound, selects compiler vs. interpreter runtime via UseCompiler during config load, and replaces preCompileWASM with a single waterv1.NewSharedDialer call.
Per-dial config isolation, dial path, and Close cleanup
protocol/water/outbound.go
Refactors dialConfig to shallow-copy the base config and install a per-dial NetworkDialerFunc using context.WithoutCancel; rewrites dialTCP to guard on sharedDialer readiness, check ctx.Err() pre/post-dial, call sharedDialer.DialContext, and wrap connections in asyncCloseConn; updates Close to shut down the shared dialer before the seeder.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 A shared runtime hops into place,
No more per-dial runtime race.
Compiler or interpreter—your pick,
Detached contexts do the trick.
One dialer rules them all with grace! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly reflects the main change: switching Water to a shared runtime.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch lanternd/water-shared-runtime

Warning

Tools execution failed with the following error:

Failed to run tools: 13 INTERNAL: Received RST_STREAM with code 2 (Internal server error)


Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
protocol/water/outbound.go (1)

269-276: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Move blocking cleanup outside o.mu and return close errors.

sharedDialer.Close and seeder.Close can block while all dial/load paths wait on o.mu; sharedDialer.Close errors are also discarded.

Proposed refactor
 func (o *Outbound) Close() error {
 	o.cancelLoad()
 	o.mu.Lock()
-	defer o.mu.Unlock()
-	if o.sharedDialer != nil {
-		_ = o.sharedDialer.Close(context.Background())
-		o.sharedDialer = nil
-	}
-	if o.seeder != nil {
-		return o.seeder.Close()
-	}
-	return nil
+	sharedDialer := o.sharedDialer
+	o.sharedDialer = nil
+	seeder := o.seeder
+	o.seeder = nil
+	o.mu.Unlock()
+
+	var err error
+	if sharedDialer != nil {
+		err = sharedDialer.Close(context.Background())
+	}
+	if seeder != nil {
+		if seederErr := seeder.Close(); err == nil {
+			err = seederErr
+		}
+	}
+	return err
 }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@protocol/water/outbound.go` around lines 269 - 276, The cleanup in the
outbound close path holds o.mu while calling sharedDialer.Close and
seeder.Close, which can block other dial/load operations, and it also ignores
sharedDialer.Close errors. Refactor the close logic in the outbound Close method
to first detach sharedDialer and seeder under o.mu, then perform the potentially
blocking Close calls after releasing the lock, and make sure any close error
from sharedDialer.Close is returned instead of discarded.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@protocol/water/outbound.go`:
- Around line 396-399: The detached dial in sharedDialer.DialContext currently
drops both caller cancellation and the caller deadline, so reapply the deadline
after context.WithoutCancel(ctx) instead of using it alone. Update the outbound
dial logic in the context-setup around rawConn/sharedDialer.DialContext so the
dial still ignores cancellation but preserves ctx’s timeout/deadline semantics.

---

Nitpick comments:
In `@protocol/water/outbound.go`:
- Around line 269-276: The cleanup in the outbound close path holds o.mu while
calling sharedDialer.Close and seeder.Close, which can block other dial/load
operations, and it also ignores sharedDialer.Close errors. Refactor the close
logic in the outbound Close method to first detach sharedDialer and seeder under
o.mu, then perform the potentially blocking Close calls after releasing the
lock, and make sure any close error from sharedDialer.Close is returned instead
of discarded.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 46c75c99-7fbf-40f9-954f-eb7a1654dc5b

📥 Commits

Reviewing files that changed from the base of the PR and between 203e7ff and 8d10a37.

⛔ Files ignored due to path filters (1)
  • go.sum is excluded by !**/*.sum
📒 Files selected for processing (3)
  • go.mod
  • option/water.go
  • protocol/water/outbound.go

Comment on lines +396 to +399
// Detach from sing-box's dial-ctx cancellation so the dial isn't torn down
// mid-flight; since it then ignores caller cancellation, honor it manually
// below.
rawConn, err = sharedDialer.DialContext(context.WithoutCancel(ctx), cfg, N.NetworkTCP, "localhost:0")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant section
sed -n '340,430p' protocol/water/outbound.go

# Find the shared dialer and related context handling
rg -n "sharedDialer\.DialContext|NetworkDialerFunc|WithoutCancel|WithDeadline|Deadline" protocol -S

# Map the file structure for the relevant file
ast-grep outline protocol/water/outbound.go --view expanded

Repository: getlantern/lantern-box

Length of output: 1967


🌐 Web query:

Go context.WithoutCancel removes deadlines documentation

💡 Result:

Yes, context.WithoutCancel removes both the cancellation signal and the deadline from the parent context [1][2]. According to the official Go documentation, context.WithoutCancel (introduced in Go 1.21) returns a derived context that points to the parent but is not canceled when the parent is canceled [1][3]. As part of this contract, the returned context explicitly returns no deadline (Deadline returns ok=false) and no error (Err returns nil), and its Done channel is nil [1][4]. By stripping both the cancellation and the deadline, context.WithoutCancel allows a task to outlive the scope of its parent request while still retaining access to request-scoped values [2]. If a specific deadline is required for the detached work, it must be added back explicitly using another constructor like context.WithTimeout or context.WithDeadline [2].

Citations:


Preserve the caller deadline when detaching the dial context. context.WithoutCancel(ctx) clears both cancellation and deadline, so this dial can outlive the request timeout unless the deadline is re-applied.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@protocol/water/outbound.go` around lines 396 - 399, The detached dial in
sharedDialer.DialContext currently drops both caller cancellation and the caller
deadline, so reapply the deadline after context.WithoutCancel(ctx) instead of
using it alone. Update the outbound dial logic in the context-setup around
rawConn/sharedDialer.DialContext so the dial still ignores cancellation but
preserves ctx’s timeout/deadline semantics.

@WendelHime WendelHime merged commit 8384c76 into main Jun 30, 2026
4 checks passed
@WendelHime WendelHime deleted the lanternd/water-shared-runtime branch June 30, 2026 20:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants