From: vanekl on
On Mar 5, 10:16 pm, vanekl <va...(a)acd.net> wrote:
> So I wrote a test program this morning. It turns out that this
> architecture is absolutely terrible even with just a few threads
> accessing the same file at the same time (which I had already assumed)
> BUT only if the file is "small." Something interesting happens when
> the size of the file increases to a "large" size. I could have 50
> threads accessing the same file and never get an integrity error. [I
> was using something similar to your 'restore' function.] I'm trying to
> figure out why this is. In any event, it surprised me.

Follow up on my tests.

I was allowing threads to randomly delete the file before doing a
save. This one op (deletion) caused data integrity errors. When I
removed this feature and made all threads do simple appends all errors
went away. I'm sure either SBCL or Debian is holding a lock, but I can
get this to work in a multithreaded environment without having to
explicitly call a mutex as long as I never delete the file and use
append mode.
From: Alex Mizrahi on
v> went away. I'm sure either SBCL or Debian is holding a lock, but I can
v> get this to work in a multithreaded environment without having to
v> explicitly call a mutex as long as I never delete the file and use
v> append mode.

Multithreading errors only manifest themselves in certain situations. If
you've checked this in one situation, this does not mean it will work in
other situations too, unless you can prove otherwise. "I'm sure that
something is holding lock" is not enough to prove that, sorry.

I think small appends are, indeed, atomic. Larger appends won't be.
I think with bufferization in place you need to write at least 4kb of data.
Maybe more.

Do you know how prin1 works? It goes throuh data structure, converts pieces
of it into strings and writes them to stream. When there is enough stuff in
buffer, it goes into a file.

Even if each write is atomic, whole prin1 operation is not. If you have
enough data, each prin1 will result in many writes when you have enough
data.

E.g. if you have 4KB buffer and two threads print 6KB of data, you can get
this in your file:

<first 4KB of thread1><first 4KB of thread2><second 2KB of thread1><second
2KB of thread2>

That is, now you have a salad instead of your data.

And I think depending on your data being small enough is just ridiculous.

From: vanekl on
More tests.
Sigh. It only works 99.9%+- of the time.

From: Giorgos Keramidas on
On Fri, 5 Mar 2010 19:38:40 -0800 (PST), vanekl <vanek(a)acd.net> wrote:
> On Mar 5, 10:16�pm, vanekl <va...(a)acd.net> wrote:
>> So I wrote a test program this morning. It turns out that this
>> architecture is absolutely terrible even with just a few threads
>> accessing the same file at the same time (which I had already assumed)
>> BUT only if the file is "small." Something interesting happens when
>> the size of the file increases to a "large" size. I could have 50
>> threads accessing the same file and never get an integrity error. [I
>> was using something similar to your 'restore' function.] I'm trying to
>> figure out why this is. In any event, it surprised me.
>
> Follow up on my tests.
>
> I was allowing threads to randomly delete the file before doing a
> save. This one op (deletion) caused data integrity errors. When I
> removed this feature and made all threads do simple appends all errors
> went away. I'm sure either SBCL or Debian is holding a lock, but I can
> get this to work in a multithreaded environment without having to
> explicitly call a mutex as long as I never delete the file and use
> append mode.

Yes, this makes sense.

Writes to a file with a buffer size smaller than the internal buffer
size of the kernel are usually 'mostly atomic'. If your kernel is using
a buffer size for the underlying write(2) call that is 4 KB and you are
appending in chunks less than 4 KB, then writes will tend to be in
order, but there are still cases where two threads can race each other,
e.g. when two threads try to seek to the end of the file to append:

1. Thread #1 seeks to current
file size (at offset K).

2. Thread #2 seeks to current file size
also at offset K.

3. Thread #1 writes a few Lisp
forms at offset K.

4. Thread #2 writes a few forms of its
own, at offset K too.

Cooperative fcntl-style locking may help, but if you start 'bloating'
the size of the small and elegant print / read functions that save and
restore forms, you might as well start pushing the forms to a very light
file-based database like SQLite.

From: vanekl on
On Mar 6, 10:15 am, Giorgos Keramidas <keram...(a)ceid.upatras.gr>
wrote:
> On Fri, 5 Mar 2010 19:38:40 -0800 (PST), vanekl <va...(a)acd.net> wrote:
> > On Mar 5, 10:16 pm, vanekl <va...(a)acd.net> wrote:
> >> So I wrote a test program this morning. It turns out that this
> >> architecture is absolutely terrible even with just a few threads
> >> accessing the same file at the same time (which I had already assumed)
> >> BUT only if the file is "small." Something interesting happens when
> >> the size of the file increases to a "large" size. I could have 50
> >> threads accessing the same file and never get an integrity error. [I
> >> was using something similar to your 'restore' function.] I'm trying to
> >> figure out why this is. In any event, it surprised me.
>
> > Follow up on my tests.
>
> > I was allowing threads to randomly delete the file before doing a
> > save. This one op (deletion) caused data integrity errors. When I
> > removed this feature and made all threads do simple appends all errors
> > went away. I'm sure either SBCL or Debian is holding a lock, but I can
> > get this to work in a multithreaded environment without having to
> > explicitly call a mutex as long as I never delete the file and use
> > append mode.
>
> Yes, this makes sense.
>
> Writes to a file with a buffer size smaller than the internal buffer
> size of the kernel are usually 'mostly atomic'.  If your kernel is using
> a buffer size for the underlying write(2) call that is 4 KB and you are
> appending in chunks less than 4 KB, then writes will tend to be in
> order, but there are still cases where two threads can race each other,
> e.g. when two threads try to seek to the end of the file to append:
>
>     1.  Thread #1 seeks to current
>         file size (at offset K).
>
>     2.                              Thread #2 seeks to current file size
>                                     also at offset K.
>
>     3. Thread #1 writes a few Lisp
>        forms at offset K.
>
>     4.                              Thread #2 writes a few forms of its
>                                     own, at offset K too.
>
> Cooperative fcntl-style locking may help, but if you start 'bloating'
> the size of the small and elegant print / read functions that save and
> restore forms, you might as well start pushing the forms to a very light
> file-based database like SQLite.


You must be right. No matter how hard I try, I cannot provoke an
integrity error when the tuple is "small." When the tuple is "large,"
I get write errors about .1% of the time.