Skip to content

Commit 9638216

Browse files
committed
dumper
1 parent 645c3b3 commit 9638216

6 files changed

Lines changed: 373 additions & 2 deletions

File tree

src/node_io_watcher.cc

Lines changed: 282 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,30 @@
22
#include <node_io_watcher.h>
33

44
#include <node.h>
5+
#include <node_buffer.h>
56
#include <v8.h>
67

8+
9+
#include <sys/uio.h> /* writev */
10+
#include <errno.h>
11+
712
#include <assert.h>
813

914
namespace node {
1015

1116
using namespace v8;
1217

18+
static ev_prepare dumper;
19+
static Persistent<Object> dump_queue;
20+
1321
Persistent<FunctionTemplate> IOWatcher::constructor_template;
1422
Persistent<String> callback_symbol;
1523

24+
static Persistent<String> next_sym;
25+
static Persistent<String> data_sym;
26+
static Persistent<String> offset_sym;
27+
static Persistent<String> buckets_sym;
28+
1629

1730
void IOWatcher::Initialize(Handle<Object> target) {
1831
HandleScope scope;
@@ -26,9 +39,23 @@ void IOWatcher::Initialize(Handle<Object> target) {
2639
NODE_SET_PROTOTYPE_METHOD(constructor_template, "stop", IOWatcher::Stop);
2740
NODE_SET_PROTOTYPE_METHOD(constructor_template, "set", IOWatcher::Set);
2841

29-
target->Set(String::NewSymbol("IOWatcher"), constructor_template->GetFunction());
42+
Local<Function> io_watcher = constructor_template->GetFunction();
43+
target->Set(String::NewSymbol("IOWatcher"), io_watcher);
3044

3145
callback_symbol = NODE_PSYMBOL("callback");
46+
47+
next_sym = NODE_PSYMBOL("next");
48+
buckets_sym = NODE_PSYMBOL("buckets");
49+
offset_sym = NODE_PSYMBOL("offset");
50+
data_sym = NODE_PSYMBOL("data");
51+
52+
53+
ev_prepare_init(&dumper, IOWatcher::Dump);
54+
ev_prepare_start(EV_DEFAULT_UC_ &dumper);
55+
ev_unref(EV_DEFAULT_UC);
56+
57+
dump_queue = Persistent<Object>::New(Object::New());
58+
io_watcher->Set(String::NewSymbol("dumpQueue"), dump_queue);
3259
}
3360

3461

@@ -143,6 +170,260 @@ Handle<Value> IOWatcher::Set(const Arguments& args) {
143170
return Undefined();
144171
}
145172

173+
#define KB 1024
174+
175+
/*
176+
* A large javascript object structure is built up in net.js. The function
177+
* Dump is called at the end of each iteration, before select() is called,
178+
* to push all the data out to sockets.
179+
*
180+
* The structure looks like this:
181+
*
182+
* IOWatcher.dumpQueue -> W -> W -> W -> W
183+
* | | | |
184+
* o o o o
185+
* | | |
186+
* o o o
187+
* | |
188+
* o o
189+
* |
190+
* o
191+
*
192+
* Where the 'W' nodes are IOWatcher instances associated with a particular
193+
* socket. The 'o' nodes are little javascript objects with a 'data'
194+
* member. 'data' is either a string or buffer. E.G.
195+
* o = { data: "hello world" }
196+
*
197+
*/
198+
199+
// To enable this debug output, do:
200+
// echo "CPPFLAGS += -DDUMP_DEBUG" >> config.mak
201+
// make clean all
202+
#ifdef DUMP_DEBUG
203+
#define DEBUG_PRINT(fmt,...) do { \
204+
fprintf(stderr, "%s:%d ", __FILE__, __LINE__); \
205+
fprintf(stderr, fmt "\n", ##__VA_ARGS__); \
206+
} while (0)
207+
#else
208+
#define DEBUG_PRINT(fmt,...)
209+
#endif
210+
211+
212+
void IOWatcher::Dump(EV_P_ ev_prepare *watcher, int revents) {
213+
assert(revents == EV_PREPARE);
214+
assert(watcher == &dumper);
215+
216+
HandleScope scope;
217+
218+
219+
#define IOV_SIZE 10000
220+
static struct iovec iov[IOV_SIZE];
221+
222+
// Loop over all 'fd objects' in the dump queue. Each object stands for a
223+
// socket that has stuff to be written out.
224+
Local<Value> writer_node_v;
225+
Local<Object> writer_node;
226+
Local<Object> writer_node_last = Local<Object>::New(dump_queue);
227+
228+
for (writer_node_v = dump_queue->Get(next_sym);
229+
writer_node_v->IsObject();
230+
writer_node_v = writer_node->Get(next_sym),
231+
writer_node_last = writer_node) {
232+
233+
writer_node = writer_node_v->ToObject();
234+
235+
IOWatcher *io = ObjectWrap::Unwrap<IOWatcher>(writer_node);
236+
// stats (just for fun)
237+
io->dumps_++;
238+
io->last_dump_ = ev_now(EV_DEFAULT_UC);
239+
240+
DEBUG_PRINT("Dumping %d", io->watcher_.fd);
241+
242+
// Number of items we've stored in iov
243+
int iovcnt = 0;
244+
// Number of bytes we've stored in iov
245+
size_t to_write = 0;
246+
247+
248+
// Offset is only so large as the first buffer of data
249+
// this occurs when a previous writev could not entirely flush
250+
// a bucket.
251+
size_t offset = 0;
252+
if (writer_node->Has(offset_sym)) {
253+
offset = writer_node->Get(offset_sym)->Uint32Value();
254+
}
255+
256+
// Loop over all the buckets for this particular socket.
257+
Local<Value> bucket_v;
258+
Local<Object> bucket;
259+
bool first = true;
260+
unsigned int bucket_index = 0;
261+
for (bucket_v = writer_node->Get(buckets_sym);
262+
bucket_v->IsObject() && to_write < 64*KB && iovcnt < IOV_SIZE;
263+
bucket_v = bucket->Get(next_sym), bucket_index++) {
264+
bucket = bucket_v->ToObject();
265+
266+
Local<Value> data_v = bucket->Get(data_sym);
267+
// net.js will be setting this 'data' value. We can ensure that it is
268+
// never empty.
269+
assert(!data_v.IsEmpty());
270+
271+
Local<Object> buf_object;
272+
273+
if (data_v->IsString()) {
274+
// FIXME This is suboptimal - Buffer::New is slow.
275+
// Also insert v8::String::Pointers() hack here.
276+
Local<String> s = data_v->ToString();
277+
buf_object = Local<Object>::New(Buffer::New(s));
278+
bucket->Set(data_sym, buf_object);
279+
} else if (Buffer::HasInstance(data_v)) {
280+
buf_object = data_v->ToObject();
281+
} else {
282+
assert(0);
283+
}
284+
285+
size_t l = Buffer::Length(buf_object);
286+
287+
if (first /* ugly */) {
288+
assert(offset < l);
289+
iov[iovcnt].iov_base = Buffer::Data(buf_object) + offset;
290+
iov[iovcnt].iov_len = l - offset;
291+
} else {
292+
iov[iovcnt].iov_base = Buffer::Data(buf_object);
293+
iov[iovcnt].iov_len = l;
294+
}
295+
to_write += iov[iovcnt].iov_len;
296+
iovcnt++;
297+
298+
first = false; // ugly
299+
}
300+
301+
ssize_t written = writev(io->watcher_.fd, iov, iovcnt);
302+
303+
DEBUG_PRINT("iovcnt: %d, to_write: %ld, written: %ld", iovcnt, to_write, written);
304+
305+
if (written < 0) {
306+
switch (errno) {
307+
#if 0
308+
case EPIPE:
309+
// What do to do with EPIPE? Somehow we should be emitting an
310+
// error event on the socket object...
311+
break;
312+
#endif
313+
314+
case EAGAIN:
315+
DEBUG_PRINT("EAGAIN");
316+
io->Start();
317+
continue;
318+
319+
default:
320+
perror("writev");
321+
continue;
322+
}
323+
}
324+
325+
// what about written == 0 ?
326+
327+
// Now drop the buckets that have been written.
328+
first = true;
329+
bucket_index = 0;
330+
331+
for (bucket_v = writer_node->Get(buckets_sym);
332+
written > 0 && bucket_v->IsObject();
333+
bucket_v = bucket->Get(next_sym), bucket_index++) {
334+
bucket = bucket_v->ToObject();
335+
assert(written > 0);
336+
337+
Local<Value> data_v = bucket->Get(data_sym);
338+
assert(!data_v.IsEmpty());
339+
340+
// At the moment we're turning all string into buffers
341+
// so we assert that this is not a string. However, when the
342+
// "Pointer patch" lands, this assert will need to be removed.
343+
assert(!data_v->IsString());
344+
// When the "Pointer patch" lands, we will need to be careful
345+
// to somehow store the length of strings that we're optimizing on
346+
// so that it need not be recalculated here. Note the "Pointer patch"
347+
// will only apply to ASCII strings - UTF8 ones will need to be
348+
// serialized onto a buffer.
349+
size_t bucket_len = Buffer::Length(data_v->ToObject());
350+
351+
DEBUG_PRINT("%ld bucket len: %ld", bucket_index, bucket_len);
352+
353+
if (first) {
354+
// Only on the first bucket does the offset matter.
355+
if (offset + written < bucket_len) {
356+
// we have not written the entire first bucket
357+
DEBUG_PRINT("%ld Only wrote part of the first buffer. "
358+
"setting watcher.offset = %ld",
359+
bucket_index,
360+
offset + written);
361+
362+
writer_node->Set(offset_sym,
363+
Integer::NewFromUnsigned(offset + written));
364+
break;
365+
} else {
366+
DEBUG_PRINT("%ld wrote the whole first bucket. discarding.",
367+
bucket_index);
368+
// We have written the entire bucket, discard it.
369+
written -= bucket_len - offset;
370+
writer_node->Set(buckets_sym, bucket->Get(next_sym));
371+
}
372+
} else {
373+
// not first
374+
375+
if (static_cast<size_t>(written) < bucket_len) {
376+
// Didn't write the whole bucket.
377+
DEBUG_PRINT("%ld Only wrote part of the buffer. "
378+
"setting watcher.offset = %ld",
379+
bucket_index,
380+
offset + written);
381+
writer_node->Set(offset_sym,
382+
Integer::NewFromUnsigned(written));
383+
break;
384+
} else {
385+
// Wrote the whole bucket, drop it.
386+
DEBUG_PRINT("%ld wrote the whole bucket. discarding.", bucket_index);
387+
written -= bucket_len - offset;
388+
written -= bucket_len;
389+
writer_node->Set(buckets_sym, bucket->Get(next_sym));
390+
}
391+
}
392+
393+
first = false;
394+
}
395+
396+
/*
397+
* Finished dumping the buckets.
398+
* If our list of buckets is empty, we can emit 'drain' (somehow?) and
399+
* forget about this socket. Nothing needs to be done.
400+
*
401+
* Otherwise we need to prepare the io_watcher to wait for the interface
402+
* to become writable again.
403+
*/
404+
405+
if (writer_node->Get(buckets_sym)->IsUndefined()) {
406+
// Emptied the queue for this socket.
407+
// Don't wait for it to become writable.
408+
io->Stop();
409+
DEBUG_PRINT("Stop watcher %d", io->watcher_.fd);
410+
411+
// Drop the writer_node from the list.
412+
writer_node_last->Set(next_sym, writer_node->Get(next_sym));
413+
414+
// Emit drain event!
415+
416+
} else {
417+
io->Start();
418+
DEBUG_PRINT("Started watcher %d", io->watcher_.fd);
419+
// current->next = new_write_queue->next
420+
421+
// new_write_queue->next = current
422+
}
423+
}
424+
}
425+
426+
146427

147428

148429
} // namespace node

src/node_io_watcher.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,15 @@ class IOWatcher : ObjectWrap {
3333
private:
3434
static void Callback(EV_P_ ev_io *watcher, int revents);
3535

36+
static void Dump(EV_P_ ev_prepare *watcher, int revents);
37+
3638
void Start();
3739
void Stop();
3840

41+
// stats. TODO: expose to js, add reset() method
42+
uint64_t dumps_;
43+
ev_tstamp last_dump_;
44+
3945
ev_io watcher_;
4046
};
4147

src/node_net.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
#include <node.h>
55
#include <node_buffer.h>
6+
#include <node_io_watcher.h>
67

78
#include <string.h>
89
#include <stdlib.h>
@@ -37,7 +38,6 @@
3738

3839
#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
3940

40-
4141
namespace node {
4242

4343
using namespace v8;

test/fixtures/dumper2.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
var assert =require('assert');
2+
var net = require('net');
3+
var IOWatcher = process.binding('io_watcher').IOWatcher;
4+
5+
var stdout = new net.Stream(1);
6+
var w = stdout._writeWatcher;
7+
8+
mb = 1024*1024;
9+
b = Buffer(mb);
10+
for (var i = 0; i < mb; i++) {
11+
b[i] = 100;
12+
}
13+
14+
15+
IOWatcher.dumpQueue.next = w;
16+
var bucket = w.buckets = { data: b };
17+
18+
for (var i = 0; i < 50; i++) {
19+
bucket = bucket.next = { data: b };
20+
}
21+
22+
/* Total size 50*(1024*1024) = 524288000 */
23+
24+
setTimeout(function () {
25+
// In the first 10 ms, we haven't pushed out the data.
26+
assert.ok(null !== IOWatcher.dumpQueue.next);
27+
}, 10);
28+
29+
process.on('exit', function () {
30+
assert.ok(!IOWatcher.dumpQueue.next);
31+
});

0 commit comments

Comments
 (0)