raindrops 0.13.0 → 0.19.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +7 -0
  2. data/.document +1 -2
  3. data/.gitattributes +4 -0
  4. data/.gitignore +1 -1
  5. data/.olddoc.yml +13 -0
  6. data/GIT-VERSION-GEN +1 -1
  7. data/GNUmakefile +1 -2
  8. data/LICENSE +3 -3
  9. data/README +28 -34
  10. data/TODO +2 -0
  11. data/archive/.gitignore +3 -0
  12. data/archive/slrnpull.conf +4 -0
  13. data/examples/linux-listener-stats.rb +1 -2
  14. data/examples/watcher_demo.ru +1 -1
  15. data/examples/yahns.conf.rb +30 -0
  16. data/examples/zbatery.conf.rb +4 -1
  17. data/ext/raindrops/extconf.rb +107 -2
  18. data/ext/raindrops/linux_inet_diag.c +94 -101
  19. data/ext/raindrops/raindrops.c +28 -7
  20. data/ext/raindrops/tcp_info.c +245 -0
  21. data/lib/raindrops.rb +1 -1
  22. data/lib/raindrops/aggregate.rb +1 -1
  23. data/lib/raindrops/aggregate/last_data_recv.rb +1 -5
  24. data/lib/raindrops/aggregate/pmq.rb +23 -17
  25. data/lib/raindrops/linux.rb +5 -6
  26. data/lib/raindrops/middleware.rb +4 -6
  27. data/lib/raindrops/middleware/proxy.rb +2 -2
  28. data/lib/raindrops/watcher.rb +13 -13
  29. data/pkg.mk +26 -50
  30. data/raindrops.gemspec +14 -21
  31. data/test/ipv6_enabled.rb +4 -4
  32. data/test/test_aggregate_pmq.rb +1 -1
  33. data/test/test_inet_diag_socket.rb +1 -1
  34. data/test/test_last_data_recv_unicorn.rb +1 -1
  35. data/test/test_linux.rb +10 -2
  36. data/test/test_linux_all_tcp_listen_stats_leak.rb +2 -2
  37. data/test/test_linux_ipv6.rb +8 -0
  38. data/test/test_raindrops.rb +1 -1
  39. data/test/{test_linux_tcp_info.rb → test_tcp_info.rb} +34 -14
  40. data/test/test_watcher.rb +15 -10
  41. metadata +59 -171
  42. data/.wrongdoc.yml +0 -6
  43. data/Rakefile +0 -28
  44. data/ext/raindrops/linux_tcp_info.c +0 -173
@@ -38,7 +38,7 @@ struct raindrops {
38
38
  };
39
39
 
40
40
  /* called by GC */
41
- static void gcfree(void *ptr)
41
+ static void rd_free(void *ptr)
42
42
  {
43
43
  struct raindrops *r = ptr;
44
44
 
@@ -51,11 +51,24 @@ static void gcfree(void *ptr)
51
51
  xfree(ptr);
52
52
  }
53
53
 
54
+ static size_t rd_memsize(const void *ptr)
55
+ {
56
+ const struct raindrops *r = ptr;
57
+
58
+ return r->drops == MAP_FAILED ? 0 : raindrop_size * r->capa;
59
+ }
60
+
61
+ static const rb_data_type_t rd_type = {
62
+ "raindrops",
63
+ { NULL, rd_free, rd_memsize, /* reserved */ },
64
+ /* parent, data, [ flags ] */
65
+ };
66
+
54
67
  /* automatically called at creation (before initialize) */
55
68
  static VALUE alloc(VALUE klass)
56
69
  {
57
70
  struct raindrops *r;
58
- VALUE rv = Data_Make_Struct(klass, struct raindrops, NULL, gcfree, r);
71
+ VALUE rv = TypedData_Make_Struct(klass, struct raindrops, &rd_type, r);
59
72
 
60
73
  r->drops = MAP_FAILED;
61
74
  return rv;
@@ -65,7 +78,7 @@ static struct raindrops *get(VALUE self)
65
78
  {
66
79
  struct raindrops *r;
67
80
 
68
- Data_Get_Struct(self, struct raindrops, r);
81
+ TypedData_Get_Struct(self, struct raindrops, &rd_type, r);
69
82
 
70
83
  if (r->drops == MAP_FAILED)
71
84
  rb_raise(rb_eStandardError, "invalid or freed Raindrops");
@@ -104,7 +117,9 @@ retry:
104
117
  r->drops = mmap(NULL, tmp,
105
118
  PROT_READ|PROT_WRITE, MAP_ANON|MAP_SHARED, -1, 0);
106
119
  if (r->drops == MAP_FAILED) {
107
- if ((errno == EAGAIN || errno == ENOMEM) && tries-- > 0) {
120
+ int err = errno;
121
+
122
+ if ((err == EAGAIN || err == ENOMEM) && tries-- > 0) {
108
123
  rb_gc();
109
124
  goto retry;
110
125
  }
@@ -140,7 +155,9 @@ static void resize(struct raindrops *r, size_t new_rd_size)
140
155
 
141
156
  rv = mremap(old_address, old_size, new_size, MREMAP_MAYMOVE);
142
157
  if (rv == MAP_FAILED) {
143
- if (errno == EAGAIN || errno == ENOMEM) {
158
+ int err = errno;
159
+
160
+ if (err == EAGAIN || err == ENOMEM) {
144
161
  rb_gc();
145
162
  rv = mremap(old_address, old_size, new_size, 0);
146
163
  }
@@ -323,7 +340,9 @@ static VALUE aref(VALUE self, VALUE index)
323
340
 
324
341
  #ifdef __linux__
325
342
  void Init_raindrops_linux_inet_diag(void);
326
- void Init_raindrops_linux_tcp_info(void);
343
+ #endif
344
+ #ifdef HAVE_TYPE_STRUCT_TCP_INFO
345
+ void Init_raindrops_tcp_info(void);
327
346
  #endif
328
347
 
329
348
  #ifndef _SC_NPROCESSORS_CONF
@@ -428,6 +447,8 @@ void Init_raindrops_ext(void)
428
447
 
429
448
  #ifdef __linux__
430
449
  Init_raindrops_linux_inet_diag();
431
- Init_raindrops_linux_tcp_info();
450
+ #endif
451
+ #ifdef HAVE_TYPE_STRUCT_TCP_INFO
452
+ Init_raindrops_tcp_info();
432
453
  #endif
433
454
  }
@@ -0,0 +1,245 @@
1
+ #include <ruby.h>
2
+ #include <sys/socket.h>
3
+ #include <netinet/in.h>
4
+ #if defined(HAVE_LINUX_TCP_H)
5
+ # include <linux/tcp.h>
6
+ #else
7
+ # if defined(HAVE_NETINET_TCP_H)
8
+ # include <netinet/tcp.h>
9
+ # endif
10
+ # if defined(HAVE_NETINET_TCP_FSM_H)
11
+ # include <netinet/tcp_fsm.h>
12
+ # endif
13
+ #endif
14
+
15
+ #ifdef HAVE_TYPE_STRUCT_TCP_INFO
16
+ #include "my_fileno.h"
17
+
18
+ CFUNC_tcp_info_tcpi_state
19
+ CFUNC_tcp_info_tcpi_ca_state
20
+ CFUNC_tcp_info_tcpi_retransmits
21
+ CFUNC_tcp_info_tcpi_probes
22
+ CFUNC_tcp_info_tcpi_backoff
23
+ CFUNC_tcp_info_tcpi_options
24
+ CFUNC_tcp_info_tcpi_snd_wscale
25
+ CFUNC_tcp_info_tcpi_rcv_wscale
26
+ CFUNC_tcp_info_tcpi_rto
27
+ CFUNC_tcp_info_tcpi_ato
28
+ CFUNC_tcp_info_tcpi_snd_mss
29
+ CFUNC_tcp_info_tcpi_rcv_mss
30
+ CFUNC_tcp_info_tcpi_unacked
31
+ CFUNC_tcp_info_tcpi_sacked
32
+ CFUNC_tcp_info_tcpi_lost
33
+ CFUNC_tcp_info_tcpi_retrans
34
+ CFUNC_tcp_info_tcpi_fackets
35
+ CFUNC_tcp_info_tcpi_last_data_sent
36
+ CFUNC_tcp_info_tcpi_last_ack_sent
37
+ CFUNC_tcp_info_tcpi_last_data_recv
38
+ CFUNC_tcp_info_tcpi_last_ack_recv
39
+ CFUNC_tcp_info_tcpi_pmtu
40
+ CFUNC_tcp_info_tcpi_rcv_ssthresh
41
+ CFUNC_tcp_info_tcpi_rtt
42
+ CFUNC_tcp_info_tcpi_rttvar
43
+ CFUNC_tcp_info_tcpi_snd_ssthresh
44
+ CFUNC_tcp_info_tcpi_snd_cwnd
45
+ CFUNC_tcp_info_tcpi_advmss
46
+ CFUNC_tcp_info_tcpi_reordering
47
+ CFUNC_tcp_info_tcpi_rcv_rtt
48
+ CFUNC_tcp_info_tcpi_rcv_space
49
+ CFUNC_tcp_info_tcpi_total_retrans
50
+
51
+ static size_t tcpi_memsize(const void *ptr)
52
+ {
53
+ return sizeof(struct tcp_info);
54
+ }
55
+
56
+ static const rb_data_type_t tcpi_type = {
57
+ "tcp_info",
58
+ { NULL, RUBY_TYPED_DEFAULT_FREE, tcpi_memsize, /* reserved */ },
59
+ /* parent, data, [ flags ] */
60
+ };
61
+
62
+ static VALUE alloc(VALUE klass)
63
+ {
64
+ struct tcp_info *info;
65
+
66
+ return TypedData_Make_Struct(klass, struct tcp_info, &tcpi_type, info);
67
+ }
68
+
69
+ /*
70
+ * call-seq:
71
+ *
72
+ * Raindrops::TCP_Info.new(tcp_socket) -> TCP_Info object
73
+ *
74
+ * Reads a TCP_Info object from any given +tcp_socket+. See the tcp(7)
75
+ * manpage and /usr/include/linux/tcp.h for more details.
76
+ */
77
+ static VALUE init(VALUE self, VALUE io)
78
+ {
79
+ int fd = my_fileno(io);
80
+ struct tcp_info *info = DATA_PTR(self);
81
+ socklen_t len = (socklen_t)sizeof(struct tcp_info);
82
+ int rc = getsockopt(fd, IPPROTO_TCP, TCP_INFO, info, &len);
83
+
84
+ if (rc != 0)
85
+ rb_sys_fail("getsockopt");
86
+
87
+ return self;
88
+ }
89
+
90
+ void Init_raindrops_tcp_info(void)
91
+ {
92
+ VALUE cRaindrops = rb_define_class("Raindrops", rb_cObject);
93
+ VALUE cTCP_Info;
94
+
95
+ /*
96
+ * Document-class: Raindrops::TCP_Info
97
+ *
98
+ * This is used to wrap "struct tcp_info" as described in tcp(7)
99
+ * and /usr/include/linux/tcp.h. The following readers methods
100
+ * are defined corresponding to the "tcpi_" fields in the
101
+ * tcp_info struct.
102
+ *
103
+ * As of raindrops 0.18.0+, this is supported on FreeBSD and OpenBSD
104
+ * systems as well as Linux, although not all fields exist or
105
+ * match the documentation, below.
106
+ *
107
+ * In particular, the +last_data_recv+ field is useful for measuring
108
+ * the amount of time a client spent in the listen queue before
109
+ * +accept()+, but only if +TCP_DEFER_ACCEPT+ is used with the
110
+ * listen socket (it is on by default in Unicorn).
111
+ *
112
+ * - state
113
+ * - ca_state
114
+ * - retransmits
115
+ * - probes
116
+ * - backoff
117
+ * - options
118
+ * - snd_wscale
119
+ * - rcv_wscale
120
+ * - rto
121
+ * - ato
122
+ * - snd_mss
123
+ * - rcv_mss
124
+ * - unacked
125
+ * - sacked
126
+ * - lost
127
+ * - retrans
128
+ * - fackets
129
+ * - last_data_sent
130
+ * - last_ack_sent
131
+ * - last_data_recv
132
+ * - last_ack_recv
133
+ * - pmtu
134
+ * - rcv_ssthresh
135
+ * - rtt
136
+ * - rttvar
137
+ * - snd_ssthresh
138
+ * - snd_cwnd
139
+ * - advmss
140
+ * - reordering
141
+ * - rcv_rtt
142
+ * - rcv_space
143
+ * - total_retrans
144
+ *
145
+ * https://kernel.org/doc/man-pages/online/pages/man7/tcp.7.html
146
+ */
147
+ cTCP_Info = rb_define_class_under(cRaindrops, "TCP_Info", rb_cObject);
148
+ rb_define_alloc_func(cTCP_Info, alloc);
149
+ rb_define_private_method(cTCP_Info, "initialize", init, 1);
150
+
151
+ /*
152
+ * Document-method: Raindrops::TCP_Info#get!
153
+ *
154
+ * call-seq:
155
+ *
156
+ * info = Raindrops::TCP_Info.new(tcp_socket)
157
+ * info.get!(tcp_socket)
158
+ *
159
+ * Update an existing TCP_Info objects with the latest stats
160
+ * from the given socket. This even allows sharing TCP_Info
161
+ * objects between different sockets to avoid garbage.
162
+ */
163
+ rb_define_method(cTCP_Info, "get!", init, 1);
164
+
165
+ DEFINE_METHOD_tcp_info_tcpi_state;
166
+ DEFINE_METHOD_tcp_info_tcpi_ca_state;
167
+ DEFINE_METHOD_tcp_info_tcpi_retransmits;
168
+ DEFINE_METHOD_tcp_info_tcpi_probes;
169
+ DEFINE_METHOD_tcp_info_tcpi_backoff;
170
+ DEFINE_METHOD_tcp_info_tcpi_options;
171
+ DEFINE_METHOD_tcp_info_tcpi_snd_wscale;
172
+ DEFINE_METHOD_tcp_info_tcpi_rcv_wscale;
173
+ DEFINE_METHOD_tcp_info_tcpi_rto;
174
+ DEFINE_METHOD_tcp_info_tcpi_ato;
175
+ DEFINE_METHOD_tcp_info_tcpi_snd_mss;
176
+ DEFINE_METHOD_tcp_info_tcpi_rcv_mss;
177
+ DEFINE_METHOD_tcp_info_tcpi_unacked;
178
+ DEFINE_METHOD_tcp_info_tcpi_sacked;
179
+ DEFINE_METHOD_tcp_info_tcpi_lost;
180
+ DEFINE_METHOD_tcp_info_tcpi_retrans;
181
+ DEFINE_METHOD_tcp_info_tcpi_fackets;
182
+ DEFINE_METHOD_tcp_info_tcpi_last_data_sent;
183
+ DEFINE_METHOD_tcp_info_tcpi_last_ack_sent;
184
+ DEFINE_METHOD_tcp_info_tcpi_last_data_recv;
185
+ DEFINE_METHOD_tcp_info_tcpi_last_ack_recv;
186
+ DEFINE_METHOD_tcp_info_tcpi_pmtu;
187
+ DEFINE_METHOD_tcp_info_tcpi_rcv_ssthresh;
188
+ DEFINE_METHOD_tcp_info_tcpi_rtt;
189
+ DEFINE_METHOD_tcp_info_tcpi_rttvar;
190
+ DEFINE_METHOD_tcp_info_tcpi_snd_ssthresh;
191
+ DEFINE_METHOD_tcp_info_tcpi_snd_cwnd;
192
+ DEFINE_METHOD_tcp_info_tcpi_advmss;
193
+ DEFINE_METHOD_tcp_info_tcpi_reordering;
194
+ DEFINE_METHOD_tcp_info_tcpi_rcv_rtt;
195
+ DEFINE_METHOD_tcp_info_tcpi_rcv_space;
196
+ DEFINE_METHOD_tcp_info_tcpi_total_retrans;
197
+
198
+ #ifdef RAINDROPS_TCP_STATES_ALL_KNOWN
199
+
200
+ /*
201
+ * Document-const: Raindrops::TCP
202
+ *
203
+ * This is a frozen hash storing the numeric values
204
+ * maps platform-independent symbol keys to
205
+ * platform-dependent numeric values. These states
206
+ * are all valid values for the Raindrops::TCP_Info#state field.
207
+ *
208
+ * The platform-independent names of the keys in this hash are:
209
+ *
210
+ * - :ESTABLISHED
211
+ * - :SYN_SENT
212
+ * - :SYN_RECV
213
+ * - :FIN_WAIT1
214
+ * - :FIN_WAIT2
215
+ * - :TIME_WAIT
216
+ * - :CLOSE
217
+ * - :CLOSE_WAIT
218
+ * - :LAST_ACK
219
+ * - :LISTEN
220
+ * - :CLOSING
221
+ *
222
+ * This is only supported on platforms where TCP_Info is supported,
223
+ * currently FreeBSD, OpenBSD, and Linux-based systems.
224
+ */
225
+ {
226
+ #define TCPSET(n,v) rb_hash_aset(tcp, ID2SYM(rb_intern(#n)), INT2NUM(v))
227
+ VALUE tcp = rb_hash_new();
228
+ TCPSET(ESTABLISHED, RAINDROPS_TCP_ESTABLISHED);
229
+ TCPSET(SYN_SENT, RAINDROPS_TCP_SYN_SENT);
230
+ TCPSET(SYN_RECV, RAINDROPS_TCP_SYN_RECV);
231
+ TCPSET(FIN_WAIT1, RAINDROPS_TCP_FIN_WAIT1);
232
+ TCPSET(FIN_WAIT2, RAINDROPS_TCP_FIN_WAIT2);
233
+ TCPSET(TIME_WAIT, RAINDROPS_TCP_TIME_WAIT);
234
+ TCPSET(CLOSE, RAINDROPS_TCP_CLOSE);
235
+ TCPSET(CLOSE_WAIT, RAINDROPS_TCP_CLOSE_WAIT);
236
+ TCPSET(LAST_ACK, RAINDROPS_TCP_LAST_ACK);
237
+ TCPSET(LISTEN, RAINDROPS_TCP_LISTEN);
238
+ TCPSET(CLOSING, RAINDROPS_TCP_CLOSING);
239
+ #undef TCPSET
240
+ OBJ_FREEZE(tcp);
241
+ rb_define_const(cRaindrops, "TCP", tcp);
242
+ }
243
+ #endif
244
+ }
245
+ #endif /* HAVE_STRUCT_TCP_INFO */
data/lib/raindrops.rb CHANGED
@@ -12,7 +12,7 @@
12
12
  # Unlike many classes in this package, the core Raindrops class is
13
13
  # intended to be portable to all reasonably modern *nix systems
14
14
  # supporting mmap(). Please let us know if you have portability
15
- # issues, patches or pull requests at mailto:raindrops@librelist.org
15
+ # issues, patches or pull requests at mailto:raindrops-public@yhbt.net
16
16
  class Raindrops
17
17
 
18
18
  # Used to represent the number of +active+ and +queued+ sockets for
@@ -1,6 +1,6 @@
1
1
  # -*- encoding: binary -*-
2
2
  #
3
- # raindrops may use the {aggregate}[http://github.com/josephruscio/aggregate]
3
+ # raindrops may use the {aggregate}[https://github.com/josephruscio/aggregate]
4
4
  # RubyGem to aggregate statistics from TCP_Info lookups.
5
5
  module Raindrops::Aggregate
6
6
  autoload :PMQ, "raindrops/aggregate/pmq"
@@ -13,10 +13,6 @@
13
13
  # - Kgio::TCPServer#kgio_accept
14
14
  # - Kgio::TCPServer#kgio_tryaccept
15
15
  module Raindrops::Aggregate::LastDataRecv
16
- # :stopdoc:
17
- TCP_Info = Raindrops::TCP_Info
18
- # :startdoc:
19
-
20
16
  # The integer value of +last_data_recv+ is sent to this object.
21
17
  # This is usually a duck type compatible with the \Aggregate class,
22
18
  # but can be *anything* that accepts the *<<* method.
@@ -78,7 +74,7 @@ def accept_nonblock
78
74
  # +last_data_recv+ to be accurate
79
75
  def count!(io)
80
76
  if io
81
- x = TCP_Info.new(io)
77
+ x = Raindrops::TCP_Info.new(io)
82
78
  @raindrops_aggregate << x.last_data_recv
83
79
  end
84
80
  io
@@ -3,10 +3,10 @@
3
3
  require "aggregate"
4
4
  require "posix_mq"
5
5
  require "fcntl"
6
- require "io/extra"
7
6
  require "thread"
7
+ require "stringio"
8
8
 
9
- # \Aggregate + POSIX message queues support for Ruby 1.9 and \Linux
9
+ # \Aggregate + POSIX message queues support for Ruby 1.9+ and \Linux
10
10
  #
11
11
  # This class is duck-type compatible with \Aggregate and allows us to
12
12
  # aggregate and share statistics from multiple processes/threads aided
@@ -14,12 +14,11 @@
14
14
  # Raindrops::LastDataRecv Rack application, but can be used independently
15
15
  # on compatible Runtimes.
16
16
  #
17
- # Unlike the core of raindrops, this is only supported on Ruby 1.9 and
18
- # Linux 2.6. Using this class requires the following additional RubyGems
17
+ # Unlike the core of raindrops, this is only supported on Ruby 1.9+ and
18
+ # Linux 2.6+. Using this class requires the following additional RubyGems
19
19
  # or libraries:
20
20
  #
21
21
  # * aggregate (tested with 0.2.2)
22
- # * io-extra (tested with 1.2.3)
23
22
  # * posix_mq (tested with 1.0.0)
24
23
  #
25
24
  # == Design
@@ -39,9 +38,9 @@ class Raindrops::Aggregate::PMQ
39
38
  # :stopdoc:
40
39
  # These constants are for Linux. This is designed for aggregating
41
40
  # TCP_INFO.
42
- RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256")
43
- WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256")
44
- UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256")
41
+ RDLOCK = [ Fcntl::F_RDLCK ].pack("s @256".freeze).freeze
42
+ WRLOCK = [ Fcntl::F_WRLCK ].pack("s @256".freeze).freeze
43
+ UNLOCK = [ Fcntl::F_UNLCK ].pack("s @256".freeze).freeze
45
44
  # :startdoc:
46
45
 
47
46
  # returns the number of dropped messages sent to a POSIX message
@@ -84,6 +83,7 @@ def initialize(params = {})
84
83
  @wr = File.open(t.path, "wb")
85
84
  @rd = File.open(t.path, "rb")
86
85
  end
86
+ @wr.sync = true
87
87
  @cached_aggregate = @aggregate
88
88
  flush_master
89
89
  @mq_send = if opts[:lossy]
@@ -142,8 +142,8 @@ def master_loop
142
142
  warn "Unhandled exception in #{__FILE__}:#{__LINE__}: #{e}"
143
143
  break
144
144
  end while true
145
- ensure
146
- flush_master
145
+ ensure
146
+ flush_master
147
147
  end
148
148
 
149
149
  # Loads the last shared \Aggregate from the master thread/process
@@ -151,7 +151,10 @@ def aggregate
151
151
  @cached_aggregate ||= begin
152
152
  flush
153
153
  Marshal.load(synchronize(@rd, RDLOCK) do |rd|
154
- IO.pread rd.fileno, rd.stat.size, 0
154
+ dst = StringIO.new
155
+ dst.binmode
156
+ IO.copy_stream(rd, dst, rd.size, 0)
157
+ dst.string
155
158
  end)
156
159
  end
157
160
  end
@@ -163,7 +166,8 @@ def flush_master
163
166
  dump = Marshal.dump @aggregate
164
167
  synchronize(@wr, WRLOCK) do |wr|
165
168
  wr.truncate 0
166
- IO.pwrite wr.fileno, dump, 0
169
+ wr.rewind
170
+ wr.write(dump)
167
171
  end
168
172
  end
169
173
 
@@ -171,24 +175,26 @@ def flush_master
171
175
  # worker thread or process
172
176
  def stop_master_loop
173
177
  sleep 0.1 until mq_send(false)
174
- rescue Errno::EINTR
175
- retry
178
+ rescue Errno::EINTR
179
+ retry
176
180
  end
177
181
 
178
182
  def lock! io, type # :nodoc:
179
183
  io.fcntl Fcntl::F_SETLKW, type
180
- rescue Errno::EINTR
181
- retry
184
+ rescue Errno::EINTR
185
+ retry
182
186
  end
183
187
 
184
188
  # we use both a mutex for thread-safety and fcntl lock for process-safety
185
189
  def synchronize io, type # :nodoc:
186
190
  @mutex.synchronize do
187
191
  begin
192
+ type = type.dup
188
193
  lock! io, type
189
194
  yield io
190
195
  ensure
191
- lock! io, UNLOCK
196
+ lock! io, type.replace(UNLOCK)
197
+ type.clear
192
198
  end
193
199
  end
194
200
  end