iobuffer 0.1.0

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.
data/CHANGES ADDED
@@ -0,0 +1,3 @@
1
+ 0.1.0:
2
+
3
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C)2008 Tony Arcieri
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README ADDED
@@ -0,0 +1,83 @@
1
+ = IO::Buffer
2
+
3
+ IO::Buffer is a fast byte queue which is primarily intended for non-blocking
4
+ I/O applications but is suitable wherever buffering is required. IO::Buffer
5
+ is compatible with Ruby 1.8.6+ and Ruby 1.9.0+
6
+
7
+ It was conceived for two reasons: it performs non-blocking buffered I/O
8
+ completely in C which removes the performance penalties associated with
9
+ implementing buffered I/O in pure Ruby, and also to mitigate performance
10
+ problems associated with M17N strings in Ruby 1.9.
11
+
12
+ == Usage
13
+
14
+ IO::Buffer provides a subset of the methods available in Strings:
15
+
16
+ >> buf = IO::Buffer.new
17
+ => #<IO::Buffer:0x12fc708>
18
+ >> buf << "foo"
19
+ => "foo"
20
+ >> buf << "bar"
21
+ => "bar"
22
+ >> buf.to_str
23
+ => "foobar"
24
+ >> buf.size
25
+ => 6
26
+
27
+ The IO::Buffer#<< method is an alias for IO::Buffer#append. A prepend method
28
+ is also available:
29
+
30
+ >> buf = IO::Buffer.new
31
+ => #<IO::Buffer:0x12f5250>
32
+ >> buf.append "bar"
33
+ => "bar"
34
+ >> buf.prepend "foo"
35
+ => "foo"
36
+ >> buf.append "baz"
37
+ => "baz"
38
+ >> buf.to_str
39
+ => "foobarbaz"
40
+
41
+ IO::Buffer#read can be used to retrieve the contents of a buffer. You can mix
42
+ reads alongside adding more data to the buffer:
43
+
44
+ >> buf = IO::Buffer.new
45
+ => #<IO::Buffer:0x12fc5f0>
46
+ >> buf << "foo"
47
+ => "foo"
48
+ >> buf.read 2
49
+ => "fo"
50
+ >> buf << "bar"
51
+ => "bar"
52
+ >> buf.read 2
53
+ => "ob"
54
+ >> buf << "baz"
55
+ => "baz"
56
+ >> buf.read 3
57
+ => "arb"
58
+
59
+ Finally, IO::Buffer provides methods for performing non-blocking I/O with the
60
+ contents of the buffer. The IO::Buffer#read_from(IO) method will read as much
61
+ data as possible from the given IO object until the read would block.
62
+
63
+ The IO::Buffer#write_to(IO) method writes the contents of the buffer to the
64
+ given IO object until either the buffer is empty or the write would block:
65
+
66
+ >> buf = IO::Buffer.new
67
+ => #<IO::Buffer:0x12fc5c8>
68
+ >> file = File.open("README")
69
+ => #<File:README>
70
+ >> buf.read_from(file)
71
+ => 1713
72
+ >> buf.to_str
73
+ => "= IO::Buffer\n\nIO::Buffer is a fast byte queue...
74
+
75
+ If the file descriptor is not ready for I/O, the Errno::EAGAIN exception is
76
+ raised indicating no I/O was performed.
77
+
78
+ == History
79
+
80
+ IO::Buffer began its life as the buffer for Rev, a high performance event
81
+ library for Ruby.
82
+
83
+ As IO::Buffer has uses outside of Rev, it was spun off into its own Gem.
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'rake'
2
+ require 'rake/clean'
3
+
4
+ Dir['tasks/**/*.rake'].each { |task| load task }
5
+
6
+ task :default => [:compile, :spec]
7
+
8
+ CLEAN.include ["**/*.o", "**/*.log", "pkg"]
9
+ CLEAN.include ["ext/Makefile", "**/iobuffer.#{Config::CONFIG['DLEXT']}"]
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>CFBundleDevelopmentRegion</key>
6
+ <string>English</string>
7
+ <key>CFBundleIdentifier</key>
8
+ <string>com.apple.xcode.dsym.conftest</string>
9
+ <key>CFBundleInfoDictionaryVersion</key>
10
+ <string>6.0</string>
11
+ <key>CFBundlePackageType</key>
12
+ <string>dSYM</string>
13
+ <key>CFBundleSignature</key>
14
+ <string>????</string>
15
+ <key>CFBundleShortVersionString</key>
16
+ <string>1.0</string>
17
+ <key>CFBundleVersion</key>
18
+ <string>1</string>
19
+ <key>dSYM_UUID</key>
20
+ <dict>
21
+ <key>i386</key>
22
+ <string>42987EDA-119E-7D89-62E9-8578E0241E6C</string>
23
+ </dict>
24
+ </dict>
25
+ </plist>
data/ext/extconf.rb ADDED
@@ -0,0 +1,6 @@
1
+ require 'mkmf'
2
+
3
+ dir_config("bytequeue")
4
+ have_library("c", "main")
5
+
6
+ create_makefile("iobuffer")
data/ext/iobuffer.c ADDED
@@ -0,0 +1,599 @@
1
+ /*
2
+ * Copyright (C) 2007-08 Tony Arcieri
3
+ * You may redistribute this under the terms of the MIT license.
4
+ * See LICENSE for details
5
+ */
6
+
7
+ #include "ruby.h"
8
+ #include "rubyio.h"
9
+
10
+ #include <assert.h>
11
+
12
+ #include <string.h>
13
+ #include <time.h>
14
+ #include <unistd.h>
15
+ #include <errno.h>
16
+
17
+ /* Macro for retrieving the file descriptor from an FPTR */
18
+ #if !HAVE_RB_IO_T || (RUBY_VERSION_MAJOR == 1 && RUBY_VERSION_MINOR == 8)
19
+ #define FPTR_TO_FD(fptr) fileno(fptr->f)
20
+ #else
21
+ #define FPTR_TO_FD(fptr) fptr->fd
22
+ #endif
23
+
24
+ /* Default number of bytes in each node's buffer. Should be >= MTU */
25
+ #define DEFAULT_NODE_SIZE 4096
26
+
27
+ struct buffer {
28
+ unsigned size, node_size;
29
+ struct buffer_node *head, *tail;
30
+ struct buffer_node *pool_head, *pool_tail;
31
+
32
+ };
33
+
34
+ struct buffer_node {
35
+ unsigned start, end;
36
+ struct buffer_node *next;
37
+ unsigned char data[0];
38
+ };
39
+
40
+ static VALUE cIO_Buffer = Qnil;
41
+
42
+ static VALUE IO_Buffer_allocate(VALUE klass);
43
+ static void IO_Buffer_mark(struct buffer *);
44
+ static void IO_Buffer_free(struct buffer *);
45
+
46
+ static VALUE IO_Buffer_initialize(int argc, VALUE *argv, VALUE self);
47
+ static VALUE IO_Buffer_clear(VALUE self);
48
+ static VALUE IO_Buffer_size(VALUE self);
49
+ static VALUE IO_Buffer_empty(VALUE self);
50
+ static VALUE IO_Buffer_append(VALUE self, VALUE data);
51
+ static VALUE IO_Buffer_prepend(VALUE self, VALUE data);
52
+ static VALUE IO_Buffer_read(int argc, VALUE *argv, VALUE self);
53
+ static VALUE IO_Buffer_to_str(VALUE self);
54
+ static VALUE IO_Buffer_read_from(VALUE self, VALUE io);
55
+ static VALUE IO_Buffer_write_to(VALUE self, VALUE io);
56
+
57
+ static struct buffer *buffer_new(void);
58
+ static void buffer_clear(struct buffer *buf);
59
+ static void buffer_free(struct buffer *buf);
60
+ static void buffer_free_pool(struct buffer *buf);
61
+ static void buffer_prepend(struct buffer *buf, char *str, unsigned len);
62
+ static void buffer_append(struct buffer *buf, char *str, unsigned len);
63
+ static void buffer_read(struct buffer *buf, char *str, unsigned len);
64
+ static void buffer_copy(struct buffer *buf, char *str, unsigned len);
65
+ static int buffer_read_from(struct buffer *buf, int fd);
66
+ static int buffer_write_to(struct buffer *buf, int fd);
67
+
68
+ /*
69
+ * High-performance I/O buffer intended for use in non-blocking programs
70
+ *
71
+ * Data is stored in as a memory-pooled linked list of equally sized
72
+ * chunks. Routines are provided for high speed non-blocking reads
73
+ * and writes from Ruby IO objects.
74
+ */
75
+ void Init_iobuffer()
76
+ {
77
+ cIO_Buffer = rb_define_class_under(rb_cIO, "Buffer", rb_cObject);
78
+ rb_define_alloc_func(cIO_Buffer, IO_Buffer_allocate);
79
+
80
+ rb_define_method(cIO_Buffer, "initialize", IO_Buffer_initialize, -1);
81
+ rb_define_method(cIO_Buffer, "clear", IO_Buffer_clear, 0);
82
+ rb_define_method(cIO_Buffer, "size", IO_Buffer_size, 0);
83
+ rb_define_method(cIO_Buffer, "empty?", IO_Buffer_empty, 0);
84
+ rb_define_method(cIO_Buffer, "<<", IO_Buffer_append, 1);
85
+ rb_define_method(cIO_Buffer, "append", IO_Buffer_append, 1);
86
+ rb_define_method(cIO_Buffer, "prepend", IO_Buffer_prepend, 1);
87
+ rb_define_method(cIO_Buffer, "read", IO_Buffer_read, -1);
88
+ rb_define_method(cIO_Buffer, "to_str", IO_Buffer_to_str, 0);
89
+ rb_define_method(cIO_Buffer, "read_from", IO_Buffer_read_from, 1);
90
+ rb_define_method(cIO_Buffer, "write_to", IO_Buffer_write_to, 1);
91
+ }
92
+
93
+ static VALUE IO_Buffer_allocate(VALUE klass)
94
+ {
95
+ return Data_Wrap_Struct(klass, IO_Buffer_mark, IO_Buffer_free, buffer_new());
96
+ }
97
+
98
+ static void IO_Buffer_mark(struct buffer *buf)
99
+ {
100
+ /* Naively discard the memory pool whenever Ruby garbage collects */
101
+ buffer_free_pool(buf);
102
+ }
103
+
104
+ static void IO_Buffer_free(struct buffer *buf)
105
+ {
106
+ buffer_free(buf);
107
+ }
108
+
109
+ /**
110
+ * call-seq:
111
+ * IO_Buffer.new(size = DEFAULT_NODE_SIZE) -> IO_Buffer
112
+ *
113
+ * Create a new IO_Buffer with linked segments of the given size
114
+ */
115
+ static VALUE IO_Buffer_initialize(int argc, VALUE *argv, VALUE self)
116
+ {
117
+ VALUE node_size_obj;
118
+ int node_size;
119
+ struct buffer *buf;
120
+
121
+ if(rb_scan_args(argc, argv, "01", &node_size_obj) == 1) {
122
+ node_size = NUM2INT(node_size_obj);
123
+
124
+ if(node_size < 1) rb_raise(rb_eArgError, "invalid buffer size");
125
+
126
+ Data_Get_Struct(self, struct buffer, buf);
127
+
128
+ /* Make sure we're not changing the buffer size after data has been allocated */
129
+ assert(!buf->head);
130
+ assert(!buf->pool_head);
131
+
132
+ buf->node_size = node_size;
133
+ }
134
+
135
+ return Qnil;
136
+ }
137
+
138
+ /**
139
+ * call-seq:
140
+ * IO_Buffer#clear -> nil
141
+ *
142
+ * Clear all data from the IO_Buffer
143
+ */
144
+ static VALUE IO_Buffer_clear(VALUE self)
145
+ {
146
+ struct buffer *buf;
147
+ Data_Get_Struct(self, struct buffer, buf);
148
+
149
+ buffer_clear(buf);
150
+
151
+ return Qnil;
152
+ }
153
+
154
+ /**
155
+ * call-seq:
156
+ * IO_Buffer#size -> Integer
157
+ *
158
+ * Return the size of the buffer in bytes
159
+ */
160
+ static VALUE IO_Buffer_size(VALUE self)
161
+ {
162
+ struct buffer *buf;
163
+ Data_Get_Struct(self, struct buffer, buf);
164
+
165
+ return INT2NUM(buf->size);
166
+ }
167
+
168
+ /**
169
+ * call-seq:
170
+ * IO_Buffer#empty? -> Boolean
171
+ *
172
+ * Is the buffer empty?
173
+ */
174
+ static VALUE IO_Buffer_empty(VALUE self)
175
+ {
176
+ struct buffer *buf;
177
+ Data_Get_Struct(self, struct buffer, buf);
178
+
179
+ return buf->size > 0 ? Qfalse : Qtrue;
180
+ }
181
+
182
+ /**
183
+ * call-seq:
184
+ * IO_Buffer#append(data) -> String
185
+ *
186
+ * Append the given data to the end of the buffer
187
+ */
188
+ static VALUE IO_Buffer_append(VALUE self, VALUE data)
189
+ {
190
+ struct buffer *buf;
191
+ Data_Get_Struct(self, struct buffer, buf);
192
+
193
+ /* Is this needed? Never seen anyone else do it... */
194
+ data = rb_convert_type(data, T_STRING, "String", "to_str");
195
+ buffer_append(buf, RSTRING_PTR(data), RSTRING_LEN(data));
196
+
197
+ return data;
198
+ }
199
+
200
+ /**
201
+ * call-seq:
202
+ * IO_Buffer#prepend(data) -> String
203
+ *
204
+ * Prepend the given data to the beginning of the buffer
205
+ */
206
+ static VALUE IO_Buffer_prepend(VALUE self, VALUE data)
207
+ {
208
+ struct buffer *buf;
209
+ Data_Get_Struct(self, struct buffer, buf);
210
+
211
+ data = rb_convert_type(data, T_STRING, "String", "to_str");
212
+ buffer_prepend(buf, RSTRING_PTR(data), RSTRING_LEN(data));
213
+
214
+ return data;
215
+ }
216
+
217
+ /**
218
+ * call-seq:
219
+ * IO_Buffer#read(length = nil) -> String
220
+ *
221
+ * Read the specified abount of data from the buffer. If no value
222
+ * is given the entire contents of the buffer are returned. Any data
223
+ * read from the buffer is cleared.
224
+ */
225
+ static VALUE IO_Buffer_read(int argc, VALUE *argv, VALUE self)
226
+ {
227
+ VALUE length_obj, str;
228
+ int length;
229
+ struct buffer *buf;
230
+
231
+ Data_Get_Struct(self, struct buffer, buf);
232
+
233
+ if(rb_scan_args(argc, argv, "01", &length_obj) == 1) {
234
+ length = NUM2INT(length_obj);
235
+ } else {
236
+ if(buf->size == 0)
237
+ return rb_str_new2("");
238
+
239
+ length = buf->size;
240
+ }
241
+
242
+ if(length > buf->size)
243
+ length = buf->size;
244
+
245
+ if(length < 1)
246
+ rb_raise(rb_eArgError, "length must be greater than zero");
247
+
248
+ str = rb_str_new(0, length);
249
+ buffer_read(buf, RSTRING_PTR(str), length);
250
+
251
+ return str;
252
+ }
253
+
254
+ /**
255
+ * call-seq:
256
+ * IO_Buffer#to_str -> String
257
+ *
258
+ * Convert the Buffer to a String. The original buffer is unmodified.
259
+ */
260
+ static VALUE IO_Buffer_to_str(VALUE self) {
261
+ VALUE str;
262
+ struct buffer *buf;
263
+
264
+ Data_Get_Struct(self, struct buffer, buf);
265
+
266
+ str = rb_str_new(0, buf->size);
267
+ buffer_copy(buf, RSTRING_PTR(str), buf->size);
268
+
269
+ return str;
270
+ }
271
+
272
+ /**
273
+ * call-seq:
274
+ * IO_Buffer#read_from(io) -> Integer
275
+ *
276
+ * Perform a nonblocking read of the the given IO object and fill
277
+ * the buffer with any data received. The call will read as much
278
+ * data as it can until the read would block.
279
+ */
280
+ static VALUE IO_Buffer_read_from(VALUE self, VALUE io) {
281
+ struct buffer *buf;
282
+ #if HAVE_RB_IO_T
283
+ rb_io_t *fptr;
284
+ #else
285
+ OpenFile *fptr;
286
+ #endif
287
+
288
+ Data_Get_Struct(self, struct buffer, buf);
289
+ GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
290
+ rb_io_set_nonblock(fptr);
291
+
292
+ return INT2NUM(buffer_read_from(buf, FPTR_TO_FD(fptr)));
293
+ }
294
+
295
+ /**
296
+ * call-seq:
297
+ * IO_Buffer#write_to(io) -> Integer
298
+ *
299
+ * Perform a nonblocking write of the buffer to the given IO object.
300
+ * As much data as possible is written until the call would block.
301
+ * Any data which is written is removed from the buffer.
302
+ */
303
+ static VALUE IO_Buffer_write_to(VALUE self, VALUE io) {
304
+ struct buffer *buf;
305
+ #if HAVE_RB_IO_T
306
+ rb_io_t *fptr;
307
+ #else
308
+ OpenFile *fptr;
309
+ #endif
310
+
311
+ Data_Get_Struct(self, struct buffer, buf);
312
+ GetOpenFile(rb_convert_type(io, T_FILE, "IO", "to_io"), fptr);
313
+ rb_io_set_nonblock(fptr);
314
+
315
+ return INT2NUM(buffer_write_to(buf, FPTR_TO_FD(fptr)));
316
+ }
317
+
318
+ /*
319
+ * Ruby bindings end here. Below is the actual implementation of
320
+ * the underlying byte queue ADT
321
+ */
322
+
323
+ /* Create a new buffer */
324
+ static struct buffer *buffer_new(void)
325
+ {
326
+ struct buffer *buf;
327
+
328
+ buf = (struct buffer *)xmalloc(sizeof(struct buffer));
329
+ buf->head = buf->tail = buf->pool_head = buf->pool_tail = 0;
330
+ buf->size = 0;
331
+ buf->node_size = DEFAULT_NODE_SIZE;
332
+
333
+ return buf;
334
+ }
335
+
336
+ /* Clear all data from a buffer */
337
+ static void buffer_clear(struct buffer *buf)
338
+ {
339
+ /* Move everything into the buffer pool */
340
+ if(!buf->pool_tail)
341
+ buf->pool_head = buf->pool_tail = buf->head;
342
+ else
343
+ buf->pool_tail->next = buf->head;
344
+
345
+ buf->head = buf->tail = 0;
346
+ buf->size = 0;
347
+ }
348
+
349
+ /* Free a buffer */
350
+ static void buffer_free(struct buffer *buf)
351
+ {
352
+ buffer_clear(buf);
353
+ buffer_free_pool(buf);
354
+
355
+ free(buf);
356
+ }
357
+
358
+ /* Free the memory pool */
359
+ static void buffer_free_pool(struct buffer *buf)
360
+ {
361
+ struct buffer_node *tmp;
362
+
363
+ while(buf->pool_head) {
364
+ tmp = buf->pool_head;
365
+ buf->pool_head = tmp->next;
366
+ free(tmp);
367
+ }
368
+
369
+ buf->pool_tail = 0;
370
+ }
371
+
372
+ /* Create a new buffer_node (or pull one from the memory pool) */
373
+ static struct buffer_node *buffer_node_new(struct buffer *buf)
374
+ {
375
+ struct buffer_node *node;
376
+
377
+ /* Pull from the memory pool if available */
378
+ if(buf->pool_head) {
379
+ node = buf->pool_head;
380
+ buf->pool_head = node->next;
381
+
382
+ if(node->next)
383
+ node->next = 0;
384
+ else
385
+ buf->pool_tail = 0;
386
+ } else {
387
+ node = (struct buffer_node *)xmalloc(sizeof(struct buffer_node) + buf->node_size);
388
+ node->next = 0;
389
+ }
390
+
391
+ node->start = node->end = 0;
392
+ return node;
393
+ }
394
+
395
+ /* Free a buffer node (i.e. return it to the memory pool) */
396
+ static void buffer_node_free(struct buffer *buf, struct buffer_node *node)
397
+ {
398
+ node->next = buf->pool_head;
399
+ buf->pool_head = node;
400
+
401
+ if(!buf->pool_tail)
402
+ buf->pool_tail = node;
403
+ }
404
+
405
+ /* Prepend data to the front of the buffer */
406
+ static void buffer_prepend(struct buffer *buf, char *str, unsigned len)
407
+ {
408
+ struct buffer_node *node, *tmp;
409
+ buf->size += len;
410
+
411
+ /* If it fits in the beginning of the head */
412
+ if(buf->head && buf->head->start >= len) {
413
+ buf->head->start -= len;
414
+ memcpy(buf->head->data + buf->head->start, str, len);
415
+ } else {
416
+ node = buffer_node_new(buf);
417
+ node->next = buf->head;
418
+ buf->head = node;
419
+ if(!buf->tail) buf->tail = node;
420
+
421
+ while(len > buf->node_size) {
422
+ memcpy(node->data, str, buf->node_size);
423
+ node->end = buf->node_size;
424
+
425
+ tmp = buffer_node_new(buf);
426
+ tmp->next = node->next;
427
+ node->next = tmp;
428
+
429
+ if(buf->tail == node) buf->tail = tmp;
430
+ node = tmp;
431
+
432
+ str += buf->node_size;
433
+ len -= buf->node_size;
434
+ }
435
+
436
+ if(len > 0) {
437
+ memcpy(node->data, str, len);
438
+ node->end = len;
439
+ }
440
+ }
441
+ }
442
+
443
+ /* Append data to the front of the buffer */
444
+ static void buffer_append(struct buffer *buf, char *str, unsigned len)
445
+ {
446
+ unsigned nbytes;
447
+ buf->size += len;
448
+
449
+ /* If it fits in the remaining space in the tail */
450
+ if(buf->tail && len <= buf->node_size - buf->tail->end) {
451
+ memcpy(buf->tail->data + buf->tail->end, str, len);
452
+ buf->tail->end += len;
453
+ return;
454
+ }
455
+
456
+ /* Empty list needs initialized */
457
+ if(!buf->head) {
458
+ buf->head = buffer_node_new(buf);
459
+ buf->tail = buf->head;
460
+ }
461
+
462
+ /* Build links out of the data */
463
+ while(len > 0) {
464
+ nbytes = buf->node_size - buf->tail->end;
465
+ if(len < nbytes) nbytes = len;
466
+
467
+ memcpy(buf->tail->data + buf->tail->end, str, nbytes);
468
+ str += nbytes;
469
+ len -= nbytes;
470
+
471
+ buf->tail->end += nbytes;
472
+
473
+ if(len > 0) {
474
+ buf->tail->next = buffer_node_new(buf);
475
+ buf->tail = buf->tail->next;
476
+ }
477
+ }
478
+ }
479
+
480
+ /* Read data from the buffer (and clear what we've read) */
481
+ static void buffer_read(struct buffer *buf, char *str, unsigned len)
482
+ {
483
+ unsigned nbytes;
484
+ struct buffer_node *tmp;
485
+
486
+ while(buf->size > 0 && len > 0) {
487
+ nbytes = buf->head->end - buf->head->start;
488
+ if(len < nbytes) nbytes = len;
489
+
490
+ memcpy(str, buf->head->data + buf->head->start, nbytes);
491
+ str += nbytes;
492
+ len -= nbytes;
493
+
494
+ buf->head->start += nbytes;
495
+ buf->size -= nbytes;
496
+
497
+ if(buf->head->start == buf->head->end) {
498
+ tmp = buf->head;
499
+ buf->head = tmp->next;
500
+ buffer_node_free(buf, tmp);
501
+
502
+ if(!buf->head) buf->tail = 0;
503
+ }
504
+ }
505
+ }
506
+
507
+ /* Copy data from the buffer without clearing it */
508
+ static void buffer_copy(struct buffer *buf, char *str, unsigned len)
509
+ {
510
+ unsigned nbytes;
511
+ struct buffer_node *node;
512
+
513
+ node = buf->head;
514
+ while(node && len > 0) {
515
+ nbytes = node->end - node->start;
516
+ if(len < nbytes) nbytes = len;
517
+
518
+ memcpy(str, node->data + node->start, nbytes);
519
+ str += nbytes;
520
+ len -= nbytes;
521
+
522
+ if(node->start + nbytes == node->end)
523
+ node = node->next;
524
+ }
525
+ }
526
+
527
+ /* Write data from the buffer to a file descriptor */
528
+ static int buffer_write_to(struct buffer *buf, int fd)
529
+ {
530
+ int bytes_written, total_bytes_written = 0;
531
+ struct buffer_node *tmp;
532
+
533
+ while(buf->head) {
534
+ bytes_written = write(fd, buf->head->data + buf->head->start, buf->head->end - buf->head->start);
535
+
536
+ /* If the write failed... */
537
+ if(bytes_written < 0) {
538
+ if(errno != EAGAIN)
539
+ rb_sys_fail("write");
540
+
541
+ return total_bytes_written;
542
+ }
543
+
544
+ total_bytes_written += bytes_written;
545
+ buf->size -= bytes_written;
546
+
547
+ /* If the write blocked... */
548
+ if(bytes_written < buf->head->end - buf->head->start) {
549
+ buf->head->start += bytes_written;
550
+ return total_bytes_written;
551
+ }
552
+
553
+ /* Otherwise we wrote the whole buffer */
554
+ tmp = buf->head;
555
+ buf->head = tmp->next;
556
+ buffer_node_free(buf, tmp);
557
+
558
+ if(!buf->head) buf->tail = 0;
559
+ }
560
+
561
+ return total_bytes_written;
562
+ }
563
+
564
+ /* Read data from a file descriptor to a buffer */
565
+ /* Append data to the front of the buffer */
566
+ static int buffer_read_from(struct buffer *buf, int fd)
567
+ {
568
+ int bytes_read, total_bytes_read = 0;
569
+ unsigned nbytes;
570
+
571
+ /* Empty list needs initialized */
572
+ if(!buf->head) {
573
+ buf->head = buffer_node_new(buf);
574
+ buf->tail = buf->head;
575
+ }
576
+
577
+ do {
578
+ nbytes = buf->node_size - buf->tail->end;
579
+ bytes_read = read(fd, buf->tail->data + buf->tail->end, nbytes);
580
+
581
+ if(bytes_read < 1) {
582
+ if(errno != EAGAIN)
583
+ rb_sys_fail("read");
584
+
585
+ return total_bytes_read;
586
+ }
587
+
588
+ total_bytes_read += bytes_read;
589
+ buf->tail->end += nbytes;
590
+ buf->size += nbytes;
591
+
592
+ if(buf->tail->end == buf->node_size) {
593
+ buf->tail->next = buffer_node_new(buf);
594
+ buf->tail = buf->tail->next;
595
+ }
596
+ } while(bytes_read == nbytes);
597
+
598
+ return total_bytes_read;
599
+ }
data/iobuffer.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+
3
+ GEMSPEC = Gem::Specification.new do |s|
4
+ s.name = "iobuffer"
5
+ s.version = "0.1.0"
6
+ s.authors = "Tony Arcieri"
7
+ s.email = "tony@medioh.com"
8
+ s.date = "2008-10-30"
9
+ s.summary = "Fast C-based buffer for non-blocking I/O"
10
+ s.platform = Gem::Platform::RUBY
11
+ s.required_ruby_version = '>= 1.8.6'
12
+
13
+ # Gem contents
14
+ s.files = Dir.glob("{lib,ext,spec,tasks}/**/*") + ['Rakefile', 'iobuffer.gemspec']
15
+
16
+ # RubyForge info
17
+ s.homepage = "http://rev.rubyforge.org"
18
+ s.rubyforge_project = "rev"
19
+
20
+ # RDoc settings
21
+ s.has_rdoc = true
22
+ s.rdoc_options = %w(--title IO::Buffer --main README --line-numbers)
23
+ s.extra_rdoc_files = ["LICENSE", "README", "CHANGES"]
24
+
25
+ # Extensions
26
+ s.extensions = Dir["ext/**/extconf.rb"]
27
+ end
@@ -0,0 +1,116 @@
1
+ require File.dirname(__FILE__) + '/../lib/iobuffer'
2
+
3
+ describe IO::Buffer do
4
+ before :each do
5
+ @buffer = IO::Buffer.new
6
+ @buffer.size.should == 0
7
+ end
8
+
9
+ it "appends data" do
10
+ @buffer.append "foo"
11
+ @buffer.size.should == 3
12
+
13
+ @buffer.append "bar"
14
+ @buffer.size.should == 6
15
+
16
+ @buffer.read.should == "foobar"
17
+ @buffer.size.should == 0
18
+ end
19
+
20
+ it "prepends data" do
21
+ @buffer.prepend "foo"
22
+ @buffer.size.should == 3
23
+
24
+ @buffer.prepend "bar"
25
+ @buffer.size.should == 6
26
+
27
+ @buffer.read.should == "barfoo"
28
+ @buffer.size.should == 0
29
+ end
30
+
31
+ it "mixes prepending and appending properly" do
32
+ source_data = %w{foo bar baz qux}
33
+ actions = permutator([:append, :prepend] * 2)
34
+
35
+ actions.each do |sequence|
36
+ sequence.each_with_index do |entry, i|
37
+ @buffer.send(entry, source_data[i])
38
+ end
39
+
40
+ @buffer.size.should == sequence.size * 3
41
+
42
+ i = 0
43
+ expected = sequence.inject('') do |str, action|
44
+ case action
45
+ when :append
46
+ str << source_data[i]
47
+ when :prepend
48
+ str = source_data[i] + str
49
+ end
50
+
51
+ i += 1
52
+ str
53
+ end
54
+
55
+ @buffer.read.should == expected
56
+ end
57
+ end
58
+
59
+ it "reads data in chunks properly" do
60
+ @buffer.append "foobarbazqux"
61
+
62
+ @buffer.read(1).should == 'f'
63
+ @buffer.read(2).should == 'oo'
64
+ @buffer.read(3).should == 'bar'
65
+ @buffer.read(4).should == 'bazq'
66
+ @buffer.read(1).should == 'u'
67
+ @buffer.read(2).should == 'x'
68
+ end
69
+
70
+ it "converts to a string" do
71
+ @buffer.append "foobar"
72
+ @buffer.to_str == "foobar"
73
+ end
74
+
75
+ it "clears data" do
76
+ @buffer.append "foo"
77
+ @buffer.prepend "bar"
78
+
79
+ @buffer.clear
80
+ @buffer.size.should == 0
81
+ @buffer.read.should == ""
82
+
83
+ @buffer.prepend "foo"
84
+ @buffer.prepend "bar"
85
+ @buffer.append "baz"
86
+
87
+ @buffer.clear
88
+ @buffer.size.should == 0
89
+ @buffer.read.should == ""
90
+ end
91
+
92
+ it "knows when it's empty" do
93
+ @buffer.should be_empty
94
+ @buffer.append "foo"
95
+ @buffer.should_not be_empty
96
+ end
97
+
98
+ #######
99
+ private
100
+ #######
101
+
102
+ def permutator(input)
103
+ output = []
104
+ return output if input.empty?
105
+
106
+ (0..input.size - 1).inject([]) do |a, n|
107
+ if a.empty?
108
+ input.each { |x| output << [x] }
109
+ else
110
+ input.each { |x| output += a.map { |y| [x, *y] } }
111
+ end
112
+
113
+ output.dup
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,20 @@
1
+ ext_so = "ext/iobuffer.#{Config::CONFIG['DLEXT']}"
2
+ ext_files = FileList[
3
+ "ext/*.c",
4
+ "ext/*.h",
5
+ "ext/extconf.rb",
6
+ "ext/Makefile",
7
+ "lib"
8
+ ]
9
+
10
+ desc "Compile the IO::Buffer extension"
11
+ task :compile => ["ext/Makefile", ext_so ]
12
+
13
+ file "ext/Makefile" => %w[ext/extconf.rb] do
14
+ Dir.chdir('ext') { ruby "extconf.rb" }
15
+ end
16
+
17
+ file ext_so => ext_files do
18
+ Dir.chdir('ext') { sh 'make' }
19
+ cp ext_so, "lib"
20
+ end
data/tasks/gem.rake ADDED
@@ -0,0 +1,6 @@
1
+ require 'rake/gempackagetask'
2
+ load File.dirname(__FILE__) + '/../iobuffer.gemspec'
3
+
4
+ Rake::GemPackageTask.new(GEMSPEC) do |pkg|
5
+ pkg.need_tar = true
6
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,16 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ FILES = Dir['spec/*_spec.rb']
4
+
5
+ desc "Run RSpec against the package's specs"
6
+ Spec::Rake::SpecTask.new(:spec) do |t|
7
+ t.spec_opts = %w(-fs -c)
8
+ t.spec_files = FILES
9
+ end
10
+
11
+ desc "Run RSpec generate a code coverage report"
12
+ Spec::Rake::SpecTask.new(:coverage) do |t|
13
+ t.spec_files = FILES
14
+ t.rcov = true
15
+ t.rcov_opts = %w[--rails --exclude gems,spec]
16
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: iobuffer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Tony Arcieri
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-10-30 00:00:00 -06:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email: tony@medioh.com
18
+ executables: []
19
+
20
+ extensions:
21
+ - ext/extconf.rb
22
+ extra_rdoc_files:
23
+ - LICENSE
24
+ - README
25
+ - CHANGES
26
+ files:
27
+ - ext/conftest.dSYM
28
+ - ext/conftest.dSYM/Contents
29
+ - ext/conftest.dSYM/Contents/Info.plist
30
+ - ext/conftest.dSYM/Contents/Resources
31
+ - ext/conftest.dSYM/Contents/Resources/DWARF
32
+ - ext/conftest.dSYM/Contents/Resources/DWARF/conftest
33
+ - ext/extconf.rb
34
+ - ext/iobuffer.c
35
+ - spec/buffer_spec.rb
36
+ - tasks/extension.rake
37
+ - tasks/gem.rake
38
+ - tasks/spec.rake
39
+ - Rakefile
40
+ - iobuffer.gemspec
41
+ - LICENSE
42
+ - README
43
+ - CHANGES
44
+ has_rdoc: true
45
+ homepage: http://rev.rubyforge.org
46
+ post_install_message:
47
+ rdoc_options:
48
+ - --title
49
+ - IO::Buffer
50
+ - --main
51
+ - README
52
+ - --line-numbers
53
+ require_paths:
54
+ - lib
55
+ required_ruby_version: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - ">="
58
+ - !ruby/object:Gem::Version
59
+ version: 1.8.6
60
+ version:
61
+ required_rubygems_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ requirements: []
68
+
69
+ rubyforge_project: rev
70
+ rubygems_version: 1.3.0
71
+ signing_key:
72
+ specification_version: 2
73
+ summary: Fast C-based buffer for non-blocking I/O
74
+ test_files: []
75
+