Skip to content

Commit 67b9bf3

Browse files
committed
Implementation read waiter for pipe
1 parent c5c692f commit 67b9bf3

File tree

4 files changed

+301
-1
lines changed

4 files changed

+301
-1
lines changed

common/pipe/README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# pipe
2+
3+
mod from go1.21.4

common/pipe/pipe.go

+243
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,243 @@
1+
// Copyright 2010 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package pipe
6+
7+
import (
8+
"io"
9+
"net"
10+
"os"
11+
"sync"
12+
"time"
13+
14+
"github.com/sagernet/sing/common/buf"
15+
)
16+
17+
// pipeDeadline is an abstraction for handling timeouts.
18+
type pipeDeadline struct {
19+
mu sync.Mutex // Guards timer and cancel
20+
timer *time.Timer
21+
cancel chan struct{} // Must be non-nil
22+
}
23+
24+
func makePipeDeadline() pipeDeadline {
25+
return pipeDeadline{cancel: make(chan struct{})}
26+
}
27+
28+
// set sets the point in time when the deadline will time out.
29+
// A timeout event is signaled by closing the channel returned by waiter.
30+
// Once a timeout has occurred, the deadline can be refreshed by specifying a
31+
// t value in the future.
32+
//
33+
// A zero value for t prevents timeout.
34+
func (d *pipeDeadline) set(t time.Time) {
35+
d.mu.Lock()
36+
defer d.mu.Unlock()
37+
38+
if d.timer != nil && !d.timer.Stop() {
39+
<-d.cancel // Wait for the timer callback to finish and close cancel
40+
}
41+
d.timer = nil
42+
43+
// Time is zero, then there is no deadline.
44+
closed := isClosedChan(d.cancel)
45+
if t.IsZero() {
46+
if closed {
47+
d.cancel = make(chan struct{})
48+
}
49+
return
50+
}
51+
52+
// Time in the future, setup a timer to cancel in the future.
53+
if dur := time.Until(t); dur > 0 {
54+
if closed {
55+
d.cancel = make(chan struct{})
56+
}
57+
d.timer = time.AfterFunc(dur, func() {
58+
close(d.cancel)
59+
})
60+
return
61+
}
62+
63+
// Time in the past, so close immediately.
64+
if !closed {
65+
close(d.cancel)
66+
}
67+
}
68+
69+
// wait returns a channel that is closed when the deadline is exceeded.
70+
func (d *pipeDeadline) wait() chan struct{} {
71+
d.mu.Lock()
72+
defer d.mu.Unlock()
73+
return d.cancel
74+
}
75+
76+
func isClosedChan(c <-chan struct{}) bool {
77+
select {
78+
case <-c:
79+
return true
80+
default:
81+
return false
82+
}
83+
}
84+
85+
type pipeAddr struct{}
86+
87+
func (pipeAddr) Network() string { return "pipe" }
88+
func (pipeAddr) String() string { return "pipe" }
89+
90+
type pipe struct {
91+
wrMu sync.Mutex // Serialize Write operations
92+
93+
// Used by local Read to interact with remote Write.
94+
// Successful receive on rdRx is always followed by send on rdTx.
95+
rdRx <-chan []byte
96+
rdTx chan<- int
97+
98+
// Used by local Write to interact with remote Read.
99+
// Successful send on wrTx is always followed by receive on wrRx.
100+
wrTx chan<- []byte
101+
wrRx <-chan int
102+
103+
once sync.Once // Protects closing localDone
104+
localDone chan struct{}
105+
remoteDone <-chan struct{}
106+
107+
readDeadline pipeDeadline
108+
writeDeadline pipeDeadline
109+
110+
newBuffer func() *buf.Buffer
111+
}
112+
113+
// Pipe creates a synchronous, in-memory, full duplex
114+
// network connection; both ends implement the Conn interface.
115+
// Reads on one end are matched with writes on the other,
116+
// copying data directly between the two; there is no internal
117+
// buffering.
118+
func Pipe() (net.Conn, net.Conn) {
119+
cb1 := make(chan []byte)
120+
cb2 := make(chan []byte)
121+
cn1 := make(chan int)
122+
cn2 := make(chan int)
123+
done1 := make(chan struct{})
124+
done2 := make(chan struct{})
125+
126+
p1 := &pipe{
127+
rdRx: cb1, rdTx: cn1,
128+
wrTx: cb2, wrRx: cn2,
129+
localDone: done1, remoteDone: done2,
130+
readDeadline: makePipeDeadline(),
131+
writeDeadline: makePipeDeadline(),
132+
}
133+
p2 := &pipe{
134+
rdRx: cb2, rdTx: cn2,
135+
wrTx: cb1, wrRx: cn1,
136+
localDone: done2, remoteDone: done1,
137+
readDeadline: makePipeDeadline(),
138+
writeDeadline: makePipeDeadline(),
139+
}
140+
return p1, p2
141+
}
142+
143+
func (*pipe) LocalAddr() net.Addr { return pipeAddr{} }
144+
func (*pipe) RemoteAddr() net.Addr { return pipeAddr{} }
145+
146+
func (p *pipe) Read(b []byte) (int, error) {
147+
n, err := p.read(b)
148+
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
149+
err = &net.OpError{Op: "read", Net: "pipe", Err: err}
150+
}
151+
return n, err
152+
}
153+
154+
func (p *pipe) read(b []byte) (n int, err error) {
155+
switch {
156+
case isClosedChan(p.localDone):
157+
return 0, io.ErrClosedPipe
158+
case isClosedChan(p.remoteDone):
159+
return 0, io.EOF
160+
case isClosedChan(p.readDeadline.wait()):
161+
return 0, os.ErrDeadlineExceeded
162+
}
163+
164+
select {
165+
case bw := <-p.rdRx:
166+
nr := copy(b, bw)
167+
p.rdTx <- nr
168+
return nr, nil
169+
case <-p.localDone:
170+
return 0, io.ErrClosedPipe
171+
case <-p.remoteDone:
172+
return 0, io.EOF
173+
case <-p.readDeadline.wait():
174+
return 0, os.ErrDeadlineExceeded
175+
}
176+
}
177+
178+
func (p *pipe) Write(b []byte) (int, error) {
179+
n, err := p.write(b)
180+
if err != nil && err != io.ErrClosedPipe {
181+
err = &net.OpError{Op: "write", Net: "pipe", Err: err}
182+
}
183+
return n, err
184+
}
185+
186+
func (p *pipe) write(b []byte) (n int, err error) {
187+
switch {
188+
case isClosedChan(p.localDone):
189+
return 0, io.ErrClosedPipe
190+
case isClosedChan(p.remoteDone):
191+
return 0, io.ErrClosedPipe
192+
case isClosedChan(p.writeDeadline.wait()):
193+
return 0, os.ErrDeadlineExceeded
194+
}
195+
196+
p.wrMu.Lock() // Ensure entirety of b is written together
197+
defer p.wrMu.Unlock()
198+
for once := true; once || len(b) > 0; once = false {
199+
select {
200+
case p.wrTx <- b:
201+
nw := <-p.wrRx
202+
b = b[nw:]
203+
n += nw
204+
case <-p.localDone:
205+
return n, io.ErrClosedPipe
206+
case <-p.remoteDone:
207+
return n, io.ErrClosedPipe
208+
case <-p.writeDeadline.wait():
209+
return n, os.ErrDeadlineExceeded
210+
}
211+
}
212+
return n, nil
213+
}
214+
215+
func (p *pipe) SetDeadline(t time.Time) error {
216+
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
217+
return io.ErrClosedPipe
218+
}
219+
p.readDeadline.set(t)
220+
p.writeDeadline.set(t)
221+
return nil
222+
}
223+
224+
func (p *pipe) SetReadDeadline(t time.Time) error {
225+
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
226+
return io.ErrClosedPipe
227+
}
228+
p.readDeadline.set(t)
229+
return nil
230+
}
231+
232+
func (p *pipe) SetWriteDeadline(t time.Time) error {
233+
if isClosedChan(p.localDone) || isClosedChan(p.remoteDone) {
234+
return io.ErrClosedPipe
235+
}
236+
p.writeDeadline.set(t)
237+
return nil
238+
}
239+
240+
func (p *pipe) Close() error {
241+
p.once.Do(func() { close(p.localDone) })
242+
return nil
243+
}

common/pipe/pipe_wait.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package pipe
2+
3+
import (
4+
"io"
5+
"net"
6+
"os"
7+
8+
"github.com/sagernet/sing/common/buf"
9+
N "github.com/sagernet/sing/common/network"
10+
)
11+
12+
var _ N.ReadWaiter = (*pipe)(nil)
13+
14+
func (p *pipe) InitializeReadWaiter(newBuffer func() *buf.Buffer) {
15+
p.newBuffer = newBuffer
16+
}
17+
18+
func (p *pipe) WaitReadBuffer() (buffer *buf.Buffer, err error) {
19+
buffer, err = p.waitReadBuffer()
20+
if err != nil && err != io.EOF && err != io.ErrClosedPipe {
21+
err = &net.OpError{Op: "read", Net: "pipe", Err: err}
22+
}
23+
return
24+
}
25+
26+
func (p *pipe) waitReadBuffer() (buffer *buf.Buffer, err error) {
27+
switch {
28+
case isClosedChan(p.localDone):
29+
return nil, io.ErrClosedPipe
30+
case isClosedChan(p.remoteDone):
31+
return nil, io.EOF
32+
case isClosedChan(p.readDeadline.wait()):
33+
return nil, os.ErrDeadlineExceeded
34+
}
35+
select {
36+
case bw := <-p.rdRx:
37+
buffer = p.newBuffer()
38+
var nr int
39+
nr, err = buffer.Write(bw)
40+
if err != nil {
41+
buffer.Release()
42+
return
43+
}
44+
p.rdTx <- nr
45+
return
46+
case <-p.localDone:
47+
return nil, io.ErrClosedPipe
48+
case <-p.remoteDone:
49+
return nil, io.EOF
50+
case <-p.readDeadline.wait():
51+
return nil, os.ErrDeadlineExceeded
52+
}
53+
}

protocol/http/handshake.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616
F "github.com/sagernet/sing/common/format"
1717
M "github.com/sagernet/sing/common/metadata"
1818
N "github.com/sagernet/sing/common/network"
19+
"github.com/sagernet/sing/common/pipe"
1920
)
2021

2122
type Handler = N.TCPConnectionHandler
@@ -102,7 +103,7 @@ func HandleConnection(ctx context.Context, conn net.Conn, reader *std_bufio.Read
102103
DialContext: func(context context.Context, network, address string) (net.Conn, error) {
103104
metadata.Destination = M.ParseSocksaddr(address)
104105
metadata.Protocol = "http"
105-
input, output := net.Pipe()
106+
input, output := pipe.Pipe()
106107
go func() {
107108
hErr := handler.NewConnection(ctx, output, metadata)
108109
if hErr != nil {

0 commit comments

Comments
 (0)