iobuffer 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
+