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 +3 -0
- data/LICENSE +19 -0
- data/README +83 -0
- data/Rakefile +9 -0
- data/ext/conftest.dSYM/Contents/Info.plist +25 -0
- data/ext/conftest.dSYM/Contents/Resources/DWARF/conftest +0 -0
- data/ext/extconf.rb +6 -0
- data/ext/iobuffer.c +599 -0
- data/iobuffer.gemspec +27 -0
- data/spec/buffer_spec.rb +116 -0
- data/tasks/extension.rake +20 -0
- data/tasks/gem.rake +6 -0
- data/tasks/spec.rake +16 -0
- metadata +75 -0
data/CHANGES
ADDED
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,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>
|
Binary file
|
data/ext/extconf.rb
ADDED
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
|
data/spec/buffer_spec.rb
ADDED
@@ -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
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
|
+
|