| skipped 31 lines |
32 | 32 | | // DialTimeout is a simple stub adapter to implement a net.Dialer with a timeout. |
33 | 33 | | func (pe *ProxyEngine) DialTimeout(network, addr string, timeout time.Duration) (net.Conn, error) { |
34 | 34 | | ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(timeout)) |
35 | | - | go func() { |
| 35 | + | go func() { // this is a goroutine that calls cancel() upon the deadline expiring to avoid context leaks |
| 36 | + | <-ctx.Done() |
| 37 | + | cancel() |
| 38 | + | }() |
| 39 | + | return pe.MysteryDialer(ctx, network, addr) |
| 40 | + | } |
| 41 | + | |
| 42 | + | func (pe *ProxyEngine) addTimeout(socksString string) string { |
| 43 | + | tout := copABuffer.Get().(*strings.Builder) |
| 44 | + | tout.WriteString(socksString) |
| 45 | + | tout.WriteString("?timeout=") |
| 46 | + | tout.WriteString(pe.GetServerTimeoutStr()) |
| 47 | + | tout.WriteRune('s') |
| 48 | + | socksString = tout.String() |
| 49 | + | discardBuffer(tout) |
| 50 | + | return socksString |
| 51 | + | } |
| 52 | + | |
| 53 | + | func (pe *ProxyEngine) popSockAndLockIt(ctx context.Context) (*Proxy, error) { |
| 54 | + | sock := pe.GetAnySOCKS(false) |
| 55 | + | socksString := sock.String() |
| 56 | + | select { |
| 57 | + | case <-ctx.Done(): |
| 58 | + | return nil, fmt.Errorf("context done: %w", ctx.Err()) |
| 59 | + | default: |
| 60 | + | if atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) { |
| 61 | + | pe.msgGotLock(socksString) |
| 62 | + | return sock, nil |
| 63 | + | } |
36 | 64 | | select { |
37 | | - | case <-ctx.Done(): |
38 | | - | cancel() |
| 65 | + | case pe.Pending <- sock: |
| 66 | + | pe.msgCantGetLock(socksString, true) |
| 67 | + | return nil, nil |
| 68 | + | default: |
| 69 | + | pe.msgCantGetLock(socksString, false) |
| 70 | + | return nil, nil |
39 | 71 | | } |
40 | | - | }() |
41 | | - | return pe.MysteryDialer(ctx, network, addr) |
| 72 | + | } |
42 | 73 | | } |
43 | 74 | | |
44 | 75 | | // MysteryDialer is a dialer function that will use a different proxy for every request. |
| skipped 10 lines |
55 | 86 | | return nil, fmt.Errorf("giving up after %d tries", max) |
56 | 87 | | } |
57 | 88 | | if err := ctx.Err(); err != nil { |
58 | | - | return nil, fmt.Errorf("context error: %v", err) |
| 89 | + | return nil, fmt.Errorf("context error: %w", err) |
59 | 90 | | } |
60 | 91 | | var sock *Proxy |
61 | | - | popSockAndLockIt: |
62 | 92 | | for { |
63 | | - | sock = pe.GetAnySOCKS(false) |
64 | | - | socksString = sock.String() |
65 | | - | select { |
66 | | - | case <-ctx.Done(): |
67 | | - | return nil, fmt.Errorf("context done: %v", ctx.Err()) |
68 | | - | default: |
69 | | - | buf := copABuffer.Get().(*strings.Builder) |
70 | | - | if atomic.CompareAndSwapUint32(&sock.lock, stateUnlocked, stateLocked) { |
71 | | - | buf.WriteString("got lock for ") |
72 | | - | buf.WriteString(socksString) |
73 | | - | break popSockAndLockIt |
74 | | - | } |
75 | | - | select { |
76 | | - | case pe.Pending <- sock: |
77 | | - | buf.WriteString("can't get lock, putting back ") |
78 | | - | buf.WriteString(socksString) |
79 | | - | pe.dbgPrint(buf) |
80 | | - | continue |
81 | | - | default: |
82 | | - | buf.WriteString("can't get lock, can't put back ") |
83 | | - | buf.WriteString(socksString) |
84 | | - | continue |
85 | | - | } |
| 93 | + | var err error |
| 94 | + | sock, err = pe.popSockAndLockIt(ctx) |
| 95 | + | if err != nil { |
| 96 | + | return nil, err |
| 97 | + | } |
| 98 | + | if sock != nil { |
| 99 | + | break |
86 | 100 | | } |
87 | 101 | | } |
88 | | - | buf := copABuffer.Get().(*strings.Builder) |
89 | | - | buf.WriteString("try dial with: ") |
90 | | - | buf.WriteString(sock.Endpoint) |
91 | | - | pe.dbgPrint(buf) |
92 | 102 | | if pe.GetServerTimeoutStr() != "-1" { |
93 | | - | tout := copABuffer.Get().(*strings.Builder) |
94 | | - | tout.WriteString("?timeout=") |
95 | | - | tout.WriteString(pe.GetServerTimeoutStr()) |
96 | | - | tout.WriteRune('s') |
| 103 | + | socksString = pe.addTimeout(socksString) |
97 | 104 | | } |
98 | 105 | | var ok bool |
99 | 106 | | if sock, ok = pe.dispenseMiddleware(sock); !ok { |
100 | | - | buf := copABuffer.Get().(*strings.Builder) |
101 | | - | buf.WriteString("failed middleware check, ") |
102 | | - | buf.WriteString(sock.String()) |
103 | | - | buf.WriteString(", cycling...") |
104 | | - | pe.dbgPrint(buf) |
| 107 | + | pe.msgFailedMiddleware(socksString) |
105 | 108 | | continue |
106 | 109 | | } |
| 110 | + | pe.msgTry(socksString) |
107 | 111 | | atomic.StoreUint32(&sock.lock, stateUnlocked) |
108 | 112 | | dialSocks := socks.Dial(socksString) |
109 | 113 | | conn, err := dialSocks(network, addr) |
110 | 114 | | if err != nil { |
111 | 115 | | count++ |
112 | | - | buf := copABuffer.Get().(*strings.Builder) |
113 | | - | buf.WriteString("unable to reach [redacted] with ") |
114 | | - | buf.WriteString(socksString) |
115 | | - | buf.WriteString(", cycling...") |
116 | | - | pe.dbgPrint(buf) |
| 116 | + | pe.msgUnableToReach(socksString) |
117 | 117 | | continue |
118 | 118 | | } |
119 | | - | buf = copABuffer.Get().(*strings.Builder) |
120 | | - | buf.WriteString("MysteryDialer using socks: ") |
121 | | - | buf.WriteString(socksString) |
122 | | - | pe.dbgPrint(buf) |
| 119 | + | pe.msgUsingProxy(socksString) |
123 | 120 | | return conn, nil |
124 | 121 | | } |
125 | 122 | | } |
| skipped 1 lines |