anytls 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 614c54295c75168e63b13a6c1cb9703321cc70bbaa672a5a3d064ed630b014ad
4
- data.tar.gz: 51446c2245624537d53fb9fcd8b49f51b3a57f3241bcfe5785eaa5206404c02e
3
+ metadata.gz: c797edcda4beba1d06a85d522a65ba7c4a3b7079955ea5640cc6f4320078e05b
4
+ data.tar.gz: e0d213605c4574257a28a0231b11dee4484db37aae667dda501532955fc4abf3
5
5
  SHA512:
6
- metadata.gz: 1da8ddbc879a82a96263c991f27cbdaf8f9ae2b4ff90a112a811ad42ab63b102e09078af8fff6b8893c121c8b5683c68a65b942907181ffe464a0c8674e012f7
7
- data.tar.gz: b3cece03e15e6593db595b7ca78e3291fecaf8eae3a1e4bc71cd73ba1b08ff22aab8a3cec5891e3eb8851e4af56b8cdfde760fc1c52197e88fa95cb79e58ddee
6
+ metadata.gz: d8d10834c474ffd7e04c325dcf00b187c2b68984af406591b947cc8dd2ec390d7cd425024d24af87d17de62e1e8217926faae0cb12e9cfc55355a54f85609052
7
+ data.tar.gz: 7799f998dd9825273c1de1dd045e64d7cfad11816f8dd1d594a105386e312a630c1cbd5ae62951d575079733048b36ca215726ab7119edb987125d9b9c33d4b1
data/CHANGELOG.md CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.5 (2026-10-06)
4
+
5
+ - 修复 v1 多流集中报告目标就绪时,操作队列未排空却进入等待导致整条会话超时的问题。
6
+ - 服务端停止时继续回收其余资源,完成全部清理尝试后再报告首个关闭或线程异常。
7
+ - UoT 与 SOCKS5 UDP ASSOCIATE 在全部必需 worker 创建成功后才报告就绪,准备失败会回滚资源。
8
+ - Client 关闭时可取消已经创建、仍在 TLS 握手或认证阶段的会话。
9
+ - 大块 Stream 写入与 Relay 使用最多 65,535 字节的片段,减少同步提交次数。
10
+ - 有界合并 HTTP chunk 包装写入,保留流式 flush、提前响应和 trailer 边界。
11
+ - UoT 非固定模式复用最近一个数字目标的解析结果,减少重复调用;域名仍逐包解析。
12
+
3
13
  ## 0.1.4 (2026-10-05)
4
14
 
5
15
  - 临时接入网络错误后继续接受连接,保留已有代理会话。
data/README.md CHANGED
@@ -4,7 +4,7 @@ Ruby 3.4+ 的 AnyTLS gem 与命令行客户端、服务端。参考版本固定
4
4
  [`anytls-go` 的 `fd6167a`](https://github.com/anytls/anytls-go/tree/fd6167acd6d73b9fa3e607659951847fbc9e6c50)。
5
5
  核心代码不依赖 Rails。
6
6
 
7
- 当前版本为 `0.1.4`,Ruby 实现使用 [MIT 许可证](LICENSE)。项目及参考代码的许可边界见
7
+ 当前版本为 `0.1.5`,Ruby 实现使用 [MIT 许可证](LICENSE)。项目及参考代码的许可边界见
8
8
  [SOURCES](docs/SOURCES.md)。
9
9
 
10
10
  ## 使用
@@ -71,6 +71,7 @@ end
71
71
  发送,不证明目标应用已处理。错误或超时后的数据不自动重放。Stream 没有独立 fd,
72
72
  不提供 `to_io` 或真正的 TCP half-close。
73
73
  远端结束后可继续排空接收数据;显式 `close` 会丢弃未读数据,即使远端已经结束。
74
+ 大块写入按最多 65,535 字节分片,每片完成后才提交下一片;排队与发送共用本次 write 的超时预算。
74
75
 
75
76
  AnyTLS 没有独立的认证成功回执;首次连接或 v1 peer 可能先返回尚未确认目标连接的
76
77
  Stream。`connection_status` 区分 `:unconfirmed`、`:confirmed`、`:failed`。远端拒绝、认证
@@ -90,6 +91,7 @@ end
90
91
  字段可承载最多 65,535 字节,空 datagram 有效。实际 UDP socket 还受地址族与系统发送
91
92
  上限约束,可能以 `EMSGSIZE` 拒绝较大的包;线协议上限不是网络发送保证。
92
93
  SOCKS5 UDP ASSOCIATE 通过同一 UoT 能力实现。
94
+ 转发资源与必需线程全部创建后才报告就绪;准备失败不会先回复成功。
93
95
 
94
96
  ```ruby
95
97
  tls = AnyTLS::TLSConfig.new(server: true, cert: "fullchain.pem", key: "private-key.pem")
@@ -102,6 +104,8 @@ session 的数量,不会主动创建连接填满池。默认最多 128 个 ses
102
104
  各 30 秒;归还 session 不提前触发空闲扫描。客户端租用空闲 session;并发请求需要时
103
105
  建立新的 session。服务端默认最多
104
106
  256 个活动 stream。`Client#stats`、`Server#stats` 返回冻结的计数快照。
107
+ `Client#close` 会取消已创建会话的在途 TLS 握手与认证;尚处于 DNS 解析或 TCP 拨号的创建操作
108
+ 仍按既有连接预算结束,迟到的结果不会交给调用者。
105
109
 
106
110
  ## 功能与边界
107
111
 
data/docs/ARCHITECTURE.md CHANGED
@@ -39,12 +39,17 @@ Client pool 同一时刻将一个 session 租给一条业务 stream,完成后
39
39
  服务端 Session 解码器支持多条 stream 的帧,入站数量另受 session 和 server 限制。
40
40
  旧空闲连接检测、心跳和 peer stream-ID 限制都在 session 生命周期中处理,不由应用代理层重试。
41
41
  池容量等待与定时回收使用独立通知;租用、归还不触发全池回收扫描,关闭会立即唤醒两类等待者。
42
+ 工厂取得 Session 后、等待 TLS 就绪前,通过登记回调交出取消句柄。池关闭时也唤醒这些
43
+ 在途 owner;它们仍计入 creating 容量,成功租出或失败回收后才退出创建计数。
44
+ DNS 与 TCP 拨号阶段尚无 Session,继续受原连接预算约束。
42
45
 
43
46
  ## 线程与 TLS 所有权
44
47
 
45
48
  Session owner 唯一持有 TLS IO 使用权;应用线程通过同步提交请求与 completion 等待写入结果。
46
49
  owner 运行具体的 AnyTLS 事件循环:处理请求、选择待发片段、推进 TLS write/read、dispatch
47
50
  完整帧、根据方向和最近 deadline 等待 TLS fd 或 wakeup socket。它不是通用调度器。
51
+ 每轮最多处理 16 个提交操作;wake 字节可合并,队列仍非空时不得进入阻塞等待。
52
+ 即使 v1 目标就绪报告无需发送帧,也必须继续处理剩余操作,同时保留每轮 TLS 读写机会。
48
53
 
49
54
  同一 `SSLSocket` 不由 reader/writer 两线程直接共用。待重试的 `SSL_write` 保持完全相同
50
55
  的数据和长度;只有成功返回正整数后才推进偏移。write 返回 WANT_WRITE 时,下一次 TLS
@@ -79,10 +84,11 @@ Session 状态锁只保护短操作;锁内不等待网络、不回调调用者
79
84
  | 单 stream 接收缓冲 | 256 KiB |
80
85
  | 单 session 接收缓冲 | 4 MiB |
81
86
  | 单 session 提交发送预算 | 1 MiB,计入编码和 padding 预留 |
82
- | Stream 数据写片段 | 16 KiB |
87
+ | Stream 数据写片段与 Relay 读取上限 | 65,535 字节 |
83
88
  | UoT 包长度字段 | 65,535 字节;实际 UDP socket 另受地址族/系统大小限制 |
84
89
  | CLI 混合入站连接 | 256 |
85
90
 
91
+ Stream 的实际写片段还会扣除帧头与 padding 预留,适配小于默认值的 session 发送预算。
86
92
  接收 owner 不能阻塞等待单个 Stream 消费。超过接收预算时终止对应 stream,并通知 peer;
87
93
  控制帧预算也不能无限增长。发送预算不足时让提交者等待。限制按 bytes 计费,不能只用
88
94
  队列条目数代表内存大小。上限约束 Ruby 可见缓冲,不宣称它是进程 RSS 的精确上界。
@@ -102,7 +108,14 @@ AnyTLS Stream,否则已接收的响应体会被丢弃。
102
108
  预算从上传完成开始,到最终响应头结束;临时响应不延长预算,响应体不受此预算限制。
103
109
  超时先记录原因再关闭上游,唤醒已经阻塞的读取者;提前响应与清理均解除等待并回收上传线程。
104
110
  HTTP 字段值仅修剪 SP/HTAB,保持重复字段及定界冲突检查。头字段写入以最多 16 KiB
105
- 缓冲合并,保留依赖的校验和字段顺序,返回前发送;消息体仍直接流式转发。
111
+ 缓冲合并,保留依赖的校验和字段顺序,返回前发送。chunked body 同样有界合并包装字节;
112
+ 沿用依赖的 flush 边界,在下一次可能阻塞的 body.read 前发送已有数据,并在 body.close
113
+ 前发送终止块与 trailer。异常退出丢弃尚未发送的包装缓冲,不重放部分发送的数据。
114
+
115
+ UoT 与 SOCKS5 UDP association 使用启动门:全部必需线程取得后才报告成功并放行转发;
116
+ 创建失败则唤醒尚未启动的线程退出、回收 socket/stream,并报告失败。后续延迟创建地址族
117
+ 资源失败也会结束转发并触发统一清理。UoT 非固定模式在每条流内最多保留一个数字 IP 与
118
+ 端口对应的解析结果,目标改变时更新;域名结果不缓存,继续逐包解析。
106
119
 
107
120
  同一 TCP 连接天然存在队首阻塞;有限缓冲只能限制 Ruby 自身的资源占用和慢消费者影响。
108
121
  当前没有扩展协议增加 per-stream credit,不承诺无限慢消费者仍可无损持续接收。
data/docs/VALIDATION.md CHANGED
@@ -25,7 +25,7 @@
25
25
 
26
26
  CI 的 Ubuntu / Ruby 4.0 任务在验证通过后上传 `anytls-gem` artifact,包含 gem 和 `SHA256SUMS`。
27
27
  发布前须确认同一提交的完整 Ruby 矩阵和 Go 互通任务均通过,下载该 artifact 并核对校验和。
28
- 运行 `bundle exec ruby script/verify_package.rb /path/to/anytls-0.1.4.gem` 可验证已有包与当前源码
28
+ 运行 `bundle exec ruby script/verify_package.rb /path/to/anytls-0.1.5.gem` 可验证已有包与当前源码
29
29
  的文件内容、元数据和依赖一致,并隔离安装该包;提供路径时不会重新构建 gem。
30
30
  GitHub Release 和 RubyGems 必须使用这一份验证过的包,发布后下载两处产物并核对 SHA256。
31
31
 
data/lib/anytls/client.rb CHANGED
@@ -28,8 +28,8 @@ module AnyTLS
28
28
  @scheme_mutex = Mutex.new
29
29
  @pool = SessionPool.new(max_sessions: max_sessions, min_idle_sessions: min_idle_sessions,
30
30
  idle_timeout: idle_timeout, idle_check_interval: idle_check_interval,
31
- reuse: reuse) do |sequence, timeout|
32
- create_session(sequence, timeout)
31
+ reuse: reuse) do |sequence, timeout, &register|
32
+ create_session(sequence, timeout, &register)
33
33
  end
34
34
  end
35
35
 
@@ -120,6 +120,7 @@ module AnyTLS
120
120
  on_idle: ->(item) { @pool.release(item) },
121
121
  on_close: ->(item) { @pool.remove(item) },
122
122
  on_padding: ->(scheme) { @scheme_mutex.synchronize { @scheme = scheme } })
123
+ yield session
123
124
  session.wait_ready(timeout: deadline.remaining)
124
125
  rescue StandardError
125
126
  session ? session.close : socket&.close
@@ -50,33 +50,55 @@ module AnyTLS
50
50
  super
51
51
  end
52
52
 
53
- # 仅在同步写出头字段期间合并小写入;不缓存消息体,结束时即发送。
54
- # 继续使用依赖的字段校验、顺序和重复字段语义。
53
+ # 头字段和 chunk 包装共用有界缓冲;字段校验、顺序和消息定界仍由依赖处理。
55
54
  def write_headers(headers)
56
- @header_buffer = +"".b
55
+ return super if @write_buffer
56
+
57
+ begin
58
+ @write_buffer = +"".b
59
+ super
60
+ flush_write_buffer
61
+ ensure
62
+ @write_buffer = nil
63
+ end
64
+ end
65
+
66
+ def write_chunked_body(...)
67
+ @write_buffer = +"".b
68
+ # 沿用依赖的 flush 边界:下一次 body.read 可能阻塞前,先发送已取得的字节。
57
69
  super
58
- bytes = @header_buffer
59
- @header_buffer = nil
60
- write_stream(bytes) unless bytes.empty?
61
70
  ensure
62
- @header_buffer = nil
71
+ @write_buffer = nil
63
72
  end
64
73
 
65
74
  private
66
75
 
67
76
  def write_stream(bytes)
68
- return super unless @header_buffer
77
+ return super unless @write_buffer
69
78
 
70
- if @header_buffer.bytesize + bytes.bytesize > 16_384
71
- super(@header_buffer) unless @header_buffer.empty?
72
- @header_buffer.clear
79
+ if @write_buffer.bytesize + bytes.bytesize > 16_384
80
+ super(@write_buffer) unless @write_buffer.empty?
81
+ @write_buffer.clear
73
82
  end
74
83
  if bytes.bytesize > 16_384
75
84
  super
76
85
  else
77
- @header_buffer << bytes
86
+ @write_buffer << bytes
78
87
  end
79
88
  end
89
+
90
+ def flush_stream
91
+ flush_write_buffer if @write_buffer
92
+ super
93
+ end
94
+
95
+ def flush_write_buffer
96
+ bytes = @write_buffer
97
+ @write_buffer = nil
98
+ write_stream(bytes) unless bytes.empty?
99
+ ensure
100
+ @write_buffer = +"".b
101
+ end
80
102
  end
81
103
 
82
104
  # 复用上传线程守候最终响应头,不增加每请求的定时器线程。
@@ -72,15 +72,14 @@ module AnyTLS
72
72
  socket = UDPSocket.new(io.addr[0] == "AF_INET6" ? Socket::AF_INET6 : Socket::AF_INET)
73
73
  socket.bind(io.addr[3], 0)
74
74
  packets = @client.open_datagrams(timeout: @handshake_timeout)
75
- reply(io, 0, Address.new(host: socket.addr[3], port: socket.addr[1]))
76
- io.timeout = nil
77
75
  source_ip = IPAddr.new(io.peeraddr[3]).native.to_s
78
76
  source_port = requested_source.port.zero? ? nil : requested_source.port
79
77
  peer = nil
80
78
  peer_lock = Mutex.new
81
79
  done = Queue.new
82
80
  workers = []
83
- workers << packet_worker(done) do
81
+ started = Queue.new
82
+ workers << packet_worker(done, started) do
84
83
  loop do
85
84
  wire, sender = socket.recvfrom(UoT::Connection::MAX_PACKET_SIZE + 263)
86
85
  next unless IPAddr.new(sender[3]).native.to_s == source_ip
@@ -105,7 +104,7 @@ module AnyTLS
105
104
  end
106
105
  end
107
106
  end
108
- workers << packet_worker(done) do
107
+ workers << packet_worker(done, started) do
109
108
  loop do
110
109
  payload, destination = packets.receive_packet
111
110
  target = peer_lock.synchronize { peer }
@@ -115,17 +114,27 @@ module AnyTLS
115
114
  socket.send(wire, 0, target[0], target[1])
116
115
  end
117
116
  end
118
- workers << packet_worker(done) { loop { io.readpartial(1024) } }
117
+ workers << packet_worker(done, started) { loop { io.readpartial(1024) } }
118
+ # 成功回复提交完整的转发资源;部分创建失败时尚未放行任何 worker。
119
+ return unless reply(io, 0, Address.new(host: socket.addr[3], port: socket.addr[1]))
120
+
121
+ io.timeout = nil
122
+ workers.size.times { started << true }
119
123
  done.pop
124
+ rescue ThreadError
125
+ reply(io, 1)
120
126
  ensure
127
+ started&.close
121
128
  socket&.close
122
129
  packets&.close
123
130
  io.close
124
131
  workers&.each(&:join)
125
132
  end
126
133
 
127
- def packet_worker(done, &block)
134
+ def packet_worker(done, started, &block)
128
135
  Thread.new do
136
+ next unless started.pop
137
+
129
138
  block.call
130
139
  rescue IOError, SystemCallError, Error, SocketError
131
140
  nil
data/lib/anytls/relay.rb CHANGED
@@ -4,7 +4,7 @@ module AnyTLS
4
4
  # 使用有界片段进行双向转发,并负责关闭两个端点。
5
5
  # 任一方向首先结束时启动清理,并等待两个转发线程退出。
6
6
  module Relay
7
- BUFFER_SIZE = 16_384
7
+ BUFFER_SIZE = Protocol::MAX_PAYLOAD
8
8
 
9
9
  # FIN 关闭整条 AnyTLS 流;任一方向 EOF 都会关闭两个端点。
10
10
  # 此处不能模拟 TCP 半关闭,否则会丢失协议状态。
data/lib/anytls/server.rb CHANGED
@@ -84,6 +84,7 @@ module AnyTLS
84
84
  def stop(grace: 5)
85
85
  deadline = Deadline.new(grace)
86
86
  owns_stop = false
87
+ failure = nil
87
88
  sessions = @mutex.synchronize do
88
89
  return if @stopped || @stop_owner == Thread.current
89
90
 
@@ -96,7 +97,11 @@ module AnyTLS
96
97
  @stop_owner = Thread.current
97
98
  @stopping = true
98
99
  @changed.broadcast
99
- @listener&.close unless @listener&.closed?
100
+ begin
101
+ @listener&.close unless @listener&.closed?
102
+ rescue StandardError => error
103
+ failure ||= error
104
+ end
100
105
  while @active_streams.positive?
101
106
  remaining = deadline.remaining
102
107
  break if remaining&.zero?
@@ -105,11 +110,30 @@ module AnyTLS
105
110
  end
106
111
  @sessions.keys
107
112
  end
108
- sessions.each { |session| session.close(wait: false) }
109
- sessions.each(&:close)
113
+ # 单个 close/join 的异常不能跳过其他资源;完成全部回收尝试后再报告首错。
114
+ sessions.each do |session|
115
+ session.close(wait: false)
116
+ rescue StandardError => error
117
+ failure ||= error
118
+ end
110
119
  workers, targets = @mutex.synchronize { [@workers.dup, @targets.keys] }
111
- targets.each { |socket| IOHelpers.close(socket) unless socket.closed? }
112
- workers.each { |thread| thread.join unless thread == Thread.current }
120
+ targets.each do |socket|
121
+ IOHelpers.close(socket) unless socket.closed?
122
+ rescue StandardError => error
123
+ failure ||= error
124
+ end
125
+ sessions.each do |session|
126
+ session.close
127
+ rescue StandardError => error
128
+ failure ||= error
129
+ end
130
+ workers.each do |thread|
131
+ thread.join unless thread == Thread.current
132
+ rescue StandardError => error
133
+ failure ||= error
134
+ end
135
+ raise failure if failure
136
+
113
137
  nil
114
138
  ensure
115
139
  if owns_stop
@@ -175,7 +199,7 @@ module AnyTLS
175
199
  @workers.select!(&:alive?)
176
200
  @workers << Thread.new do
177
201
  handle_stream(stream, session)
178
- rescue Error, IOError, SystemCallError, SocketError => error
202
+ rescue Error, IOError, SystemCallError, SocketError, ThreadError => error
179
203
  Log.emit(@logger, :debug, "AnyTLS outbound ended: #{error.class}")
180
204
  ensure
181
205
  begin
@@ -217,7 +241,7 @@ module AnyTLS
217
241
  reported = true
218
242
  Relay.copy(stream, target)
219
243
  end
220
- rescue Error, IOError, SystemCallError, SocketError => error
244
+ rescue Error, IOError, SystemCallError, SocketError, ThreadError => error
221
245
  session.report_connected(stream.id, error: error) unless reported || stream.closed?
222
246
  raise
223
247
  ensure
@@ -247,6 +247,10 @@ module AnyTLS
247
247
  end
248
248
 
249
249
  def wait_for_work(read_wait)
250
+ # wake 字节可以被合并排空,不能把它当作每个操作各有一次的通知。
251
+ # 每轮仍只处理有限操作并推进 TLS;队列未空时直接进入下一轮。
252
+ return if @mutex.synchronize { !@operations.empty? || @stopping }
253
+
250
254
  readers = [@wake_reader]
251
255
  writers = []
252
256
  if @current && @transport.write_wait == :wait_writable
@@ -542,7 +546,11 @@ module AnyTLS
542
546
  end
543
547
 
544
548
  def register_stream(id)
545
- stream = Stream.new(self, id, receive_limit: @receive_limit, receive_budget: @receive_budget)
549
+ # 大帧仍须容纳于会话预算;较小的自定义预算保留原有分片发送能力。
550
+ payload_limit = @send_limit - Protocol::HEADER_SIZE - (@server ? 0 : @scheme.max_padding_bytes)
551
+ write_limit = payload_limit.clamp(1, Protocol::MAX_PAYLOAD)
552
+ stream = Stream.new(self, id, receive_limit: @receive_limit, receive_budget: @receive_budget,
553
+ write_limit: write_limit)
546
554
  @mutex.synchronize do
547
555
  @streams[id] = stream
548
556
  @active_count = @streams.size
@@ -31,6 +31,7 @@ module AnyTLS
31
31
  @changed = ConditionVariable.new
32
32
  @maintenance_changed = ConditionVariable.new
33
33
  @sessions = {}
34
+ @pending_sessions = {}
34
35
  @idle = {}
35
36
  @creating = 0
36
37
  @sequence = 0
@@ -68,10 +69,18 @@ module AnyTLS
68
69
 
69
70
  session = nil
70
71
  begin
71
- session = @factory.call(sequence, deadline.remaining)
72
+ session = @factory.call(sequence, deadline.remaining) do |pending|
73
+ # 工厂一旦取得 TLS session 就登记取消句柄;握手完成前也能由 close 唤醒 owner。
74
+ @mutex.synchronize do
75
+ raise ClosedError, "client closed during dial" if @closed
76
+
77
+ @pending_sessions[sequence] = pending
78
+ end
79
+ end
72
80
  @mutex.synchronize do
73
81
  raise ClosedError, "client closed during dial" if @closed || session.closed?
74
82
 
83
+ @pending_sessions.delete(sequence)
75
84
  @sessions[session] = :leased
76
85
  end
77
86
  session
@@ -80,6 +89,7 @@ module AnyTLS
80
89
  raise
81
90
  ensure
82
91
  @mutex.synchronize do
92
+ @pending_sessions.delete(sequence)
83
93
  @creating -= 1
84
94
  @changed.broadcast
85
95
  end
@@ -125,8 +135,9 @@ module AnyTLS
125
135
  @closed = true
126
136
  @changed.broadcast
127
137
  @maintenance_changed.broadcast
128
- result = @sessions.keys
138
+ result = (@sessions.keys + @pending_sessions.values).uniq
129
139
  @sessions.clear
140
+ @pending_sessions.clear
130
141
  @idle.clear
131
142
  result
132
143
  end
data/lib/anytls/stream.rb CHANGED
@@ -7,10 +7,15 @@ module AnyTLS
7
7
  class Stream
8
8
  attr_reader :id
9
9
 
10
- def initialize(session, id, receive_limit: 262_144, receive_budget: nil)
10
+ def initialize(session, id, receive_limit: 262_144, receive_budget: nil, write_limit: Protocol::MAX_PAYLOAD)
11
+ unless write_limit.is_a?(Integer) && write_limit.between?(1, Protocol::MAX_PAYLOAD)
12
+ raise ArgumentError, "write limit must be between 1 and 65535"
13
+ end
14
+
11
15
  @session = session
12
16
  @id = id
13
17
  @buffer = ByteBuffer.new(limit: receive_limit, budget: receive_budget)
18
+ @write_limit = write_limit
14
19
  @mutex = Mutex.new
15
20
  @changed = ConditionVariable.new
16
21
  @closed = false
@@ -63,7 +68,7 @@ module AnyTLS
63
68
  offset = 0
64
69
  while offset < bytes.bytesize
65
70
  ensure_open!
66
- chunk = bytes.byteslice(offset, 16_384).b.freeze
71
+ chunk = bytes.byteslice(offset, @write_limit).b.freeze
67
72
  @session.write_stream(id, chunk, deadline: deadline)
68
73
  offset += chunk.bytesize
69
74
  end
@@ -20,6 +20,7 @@ module AnyTLS
20
20
  lock = Mutex.new
21
21
  finished = Queue.new
22
22
  workers = []
23
+ started = Queue.new
23
24
  stopping = false
24
25
  create_socket = lambda do |family|
25
26
  lock.synchronize do
@@ -33,6 +34,9 @@ module AnyTLS
33
34
  connected_address.ip_port)
34
35
  end
35
36
  workers << Thread.new do
37
+ started.pop
38
+ next unless ready_reported
39
+
36
40
  loop do
37
41
  payload, sender = socket.recvfrom(Connection::MAX_PACKET_SIZE + 1)
38
42
  address = connection.destination || Address.new(host: sender[3], port: sender[1])
@@ -48,20 +52,37 @@ module AnyTLS
48
52
  end
49
53
  end
50
54
  end
51
- # 确认流连接成功前,先建立真实出站 socket。
55
+ # 所有必需线程创建成功后才确认就绪;启动门防止部分准备完成时提前转发。
52
56
  create_socket.call(connected_address ? connected_address.afamily : Socket::AF_INET)
53
- on_ready&.call
54
- ready_reported = true
55
57
  workers << Thread.new do
58
+ started.pop
59
+ next unless ready_reported
60
+
61
+ cached_destination = cached_address = nil
56
62
  loop do
57
63
  payload, destination = connection.receive_packet
58
- address = connected_address || resolve(destination)
64
+ address = connected_address
65
+ unless address
66
+ if destination == cached_destination
67
+ address = cached_address
68
+ else
69
+ address = resolve(destination)
70
+ # 每条流只保留一个数字目标,包含端口;域名继续逐包解析,不引入 DNS 过期语义。
71
+ if destination.ip?
72
+ cached_destination = destination
73
+ cached_address = address
74
+ end
75
+ end
76
+ end
59
77
  socket = create_socket.call(address.afamily)
60
78
  connected_address ? socket.send(payload, 0) : socket.send(payload, 0, address.to_sockaddr)
61
79
  end
62
- rescue IOError, SystemCallError, Error, SocketError => error
80
+ rescue IOError, SystemCallError, Error, SocketError, ThreadError => error
63
81
  finished << error
64
82
  end
83
+ on_ready&.call
84
+ ready_reported = true
85
+ started.close
65
86
  finished.pop
66
87
  nil
67
88
  rescue StandardError => error
@@ -75,6 +96,8 @@ module AnyTLS
75
96
  end
76
97
  raise
77
98
  ensure
99
+ # 准备失败时同样唤醒已创建的线程;未确认就绪的线程直接退出。
100
+ started&.close
78
101
  lock&.synchronize do
79
102
  stopping = true
80
103
  sockets&.each_value(&:close)
@@ -2,5 +2,5 @@
2
2
 
3
3
  module AnyTLS
4
4
  # gemspec、命令行和对端标识共用的唯一版本来源。
5
- VERSION = "0.1.4"
5
+ VERSION = "0.1.5"
6
6
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: anytls
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - AnyTLS Ruby contributors