Yes, in so far that you won't know if the write actually happened.
Still, you shouldn't be seeing garbage. Would it be possible to
distill a test case from your application?
You should use fs.WriteStream if possible. It's possible for
fs.write() to return saying that it had only written a few of the
bytes - and that you must call it again. fs.WriteStream takes care of
this for you for append-only files (or at least it's a bug if it does
not)
--i
> --
> You received this message because you are subscribed to the Google Groups "nodejs" group.
> To post to this group, send email to nod...@googlegroups.com.
> To unsubscribe from this group, send email to nodejs+un...@googlegroups.com.
> For more options, visit this group at http://groups.google.com/group/nodejs?hl=en.
>
>
Well, it would happen if you were creating a WriteStream for each
request, rather than having all the requests use the same WriteStream
object.
Consider this:
http.createServer(function (req, res) {
var s = fs.createWriteStream("file.log")
s.write(JSON.stringify({foo:"bar"}) + "\n")
s.end()
res.writeHead(200, {})
res.end("ok")
}).listen(80)
The timeline goes:
1. server starts.
2. client A connects, creates write stream, calls write()
3. '{"fo' gets written to disk, the rest is queued. WriteStreamA
returns false (meaning not all data was flushed).
4. client B connects, creates write stream, calls write()
5. '{"foo":"b' gets written to disk, the rest is queued. WriteStreamB
returns false.
6. WriteStreamA writes 'oo":"bar"}\n' to disk, emits "drain"
7. WriteStreamB writes 'ar"}\n' to disk, emits "drain"
Resulting file:
{"fo{"foo":"bao":"bar"}
ar"}
Alternatively, if the client is uploading the data, and that's what
you're writing, you may run into problems as well if you don't buffer
the data.
var s = fs.createWriteStream("file.log")
http.createServer(function (req, res) {
req.on("data", function (chunk) {
if (!s.write(chunk)) {
req.pause()
s.on("drain", function D () {
req.resume()
s.removeListener("drain", D)
})
}
})
res.writeHead(200, {})
res.end("ok")
})
In short, you can't assume that any write succeeded, or succeeded
completely, and you also can't assume that the client will upload its
entire payload in a single "data" event.
--i
--i
Hi billywhizz,
I think that we've found a bug here.
In short, I think that the problem is that upon return from the fs.write() call, no reference to the buffer passed to the C library's write call is retained anywhere. And we all know what the fate of unreferenced JS objects is...
It seems to me that this bug may be present in other methods of the fs module, and perhaps in other modules too. Who knows ?
It's a nasty bug because one can't tell for sure when it's going to bite.
I'm working on a patch. This could be a way to fix it :
Yep, that's it. Good catch, Jorge.
fs.read() / Read() also suffers from this but the other functions in
node_file.cc appear to be safe - libeio makes a copy of the string
arguments.
This should probably be solved by turning the Local<Buffer> into a
Persistent<Buffer> for the duration of the async operation.
Ok. A persistent handle for the buffer object.
The callback's persistent handle is made by a call to cb_persist(callback) which is then passed to eio_write() into eio_req-> data. Upon completion, libev calls After() which cb_unwrap()s req->data and cb_destroy()s it (the persistent handle).
The question is: now we'd need pass 2 handles into eio_req-> data : the one for the callback plus another one for the buffer, so that in After() we can destroy them.
What I'd do is:
malloc() a struct to hold both, pass its pointer into eio_req-> data, then in After() destroy the handles (eio_req-> data.cb and eio_req-> data.buf) and free() the struct's *.
Does it sound ~ good ?
How would you do it ?
Thanks,
--
Jorge.
Sent from my fridge
1. instead of passing a persistent function handle, it passes a
persistent object, which has a "cb" member.
2. pass the object to the callback
3. hang the buffer on the object as well.
Then I wrote a test to do this:
1. write a kb of "a" 1000 times to a file.
2. on each cb, assert that the object's buffer member is the same as
the buffer I wrote.
3. walk through each buffer in the cb, and assert that every byte is 97.
4. after the last write, walk through the file contents and verify
that every byte is 97.
The write cb assert passes.
Bad data ends up in the file.
This is absolutely, definitely, without any shadow of a doubt, *not* a
result of the buffer being GC'ed while the request is on the thread
pool.
When I stack the writes so that each one waits for the previous to
complete first, then you end up with a few failures, but no bad data.
I think this is an issue with too many eio_write calls getting piled up, maybe?
--i
Seems to work for me, I get no bad data (repeatedly, of course).
$ perl -ne '/^a{80}\n$/ or print' out.txt | wc -l
0
I did notice another bug where Node quits before libeio's task queue
has drained so you end up with a out.txt that is anywhere between 0
and 82944 bytes.
Yeah, I see bad data here too:
$ cat -v out.txt | grep -aEv '^a{1,}$'
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
But, the tests we have been doing before ( wrong.js ) were *appending* to the file, and now you instead of 'a' are opening it 'w'.
That there's (was) a problem with the dangling buffers when appending, is for sure ( try passing just noop to see it reappear ). And there's another problem when writing-a-lot-in-parallel to a file opened 'w'. That's 2 different problemas, I think.
I've touched your patch a bit.
The idea is that you can now pass ( *from JS* ) either a function or an object, like so:
binding.write(fd, buffer, offset, length, position, {cb: callback || noop, buf: buffer}); // An object
or like so:
binding.write(fd, buffer, offset, length, position, callback || noop); // A function
If there's any reference ( in addition to the callback ) you want/need/would like to refcount+1 for the duration of the call, just refer to it in the object. If not, just pass the function alone.
In any case ASYNC_CALL will obj_persist whatever you pass.
Later, upon arrival to After(), it checks whether the ref was a function or an object. If it was a function the steps to get the callback are ~= before, if it's an object, we expect the callback to be in the .cb property.
The main difference with your patch is that you can refcount+1 as many things as you want, and that the decision is made in JSLand.
I've rewritten the test to checks the syntax of each and every line, and even the order. But does it in 'a' mode. There's not any garbage, but the write()s are not always executed in the same order as they're requested. Is there anything we can do to enforce the proper order ?
Please take a look at it. What do you think ?
44fa89cda815e749cda9493ea313ef3679689a61 Fix applied in v0.2
cea3a95f9fe6faaa504542d4f03349739d08a0f3 Fix applied to master
(Two patches were needed because of the Buffer changes in master)
These fixes will be in the v0.2.5 and v0.3.1 releases (should be out
later today)
I believe there is a bug in OSX (a race condition in the file system)
which causes this to fail. If you can get
test/simple/test-fs-sir-writes-alot.js to fail on a non-macintosh
system - please report.
In the meantime here's a fix
It's a bug that's not a bug, or something...
http://lwn.net/Articles/180387/
--
Jorge.
And, Ryan, given http://lwn.net/Articles/180396/ , I think the mutex/lock patch -only for macs- would be the way to go. What do you think ?
--
Jorge.
The issue discussed in LWN is a similar issue but not the same. I do
not expect write() to be thread safe - I expect it to write to
whatever position in the file. That is, the file is of undefined
length after the operation in the test. However, I do not expect
multiple writes to introduce random data into the file as is happening
on macintosh.
I think your lock patch is reasonable. I will test it.
The problem on the macs ISTM is that some write()s properly update the file pointer but end up writing the data somewhere else, leaving 'holes' of strings of zeroes of data.length length.
> I think your lock patch is reasonable. I will test it.
commit 6aa92d5289996780834e Yahooooooo ! One more contribution by Jorge. (•jorge: is happy)
Could you please revisit/reconsider the über-awesome sys^H^H^Hutil.inspect patch thing, too ?
--
Jorge.
Shouldn't this be solved by always, always, always calling pwrite()?
Using write() in a concurrent environment is asking for trouble.
Istm that this :
pwrite(fd, data, data.length, fd.filepointer);
fd.filepointer+= data.length;
(without any mutex) isn't thread-safe (not at all), but this instead :
write(fd, data, data.length);
ought to be thread-safe (and Linus has taken care -personally- to make it so in ~2006).
It seems that Apple instead has not, at least not yet.
--
Jorge.