lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


On Sat, Jul 19, 2008 at 10:32 PM, Asko Kauppi <askok@dnainternet.net> wrote:

> The doc is _mostly_ on par but unfortunately the change log was lagging
> behind.

Oh! My apologies, I've accidentally looked into older version Lanes archive. :-(

BTW, while we're on the docs: I'd like to see short thruoughtly
commented specific examples in each section -- at least so that I
would not need to scroll up to the "sample with communications" and
guess which is which.

> Here's a better one:

>        • lanes.new (renamed from lanes.prepare)

IMHO, more Lua-ish would be lanes.create(). (Just nitpicking.)
Also see note below on "calling convention".

>        • h:unpack() (renamed from h:results)

Neither unpack() nor results() does not suggest that the function
"makes sure lane has finished" (i.e. blocking behaviour).

Also I consider mix of lane handle and lane results to be harmful. I'd
suggest to change it as follows:

 local f = lanes.new(function(<...>) <...> end)
 local lane_h = f(<...>)
 local results = { lane_h:join(<timeout>) } -- Borrowing term from pthreads.

If you create results table anyway, join may return it directly.
Otherwise, IMO, it is more elegant to use multiple return values. The
rest of lane handle members may be kept in place.

>        • Lindas for all inter-thread communications (replaces FIFOs, tubes,
> thread groups)

I'd place a link to some page, describing what Linda is in the docs
(or provide a short inline definition; better both). Is there a better
description than on Wikipedia?

 http://en.wikipedia.org/wiki/Linda_(coordination_language)

Also I'd like to see more elaborate section on data passing,
explaining performance issues etc.

>        • Fast inter-state copy using hidden "keeper states" (replaces
> serialization)

Interesting. Care to elaborate?

>        • Also closures can be copied between states (yes, upvalues copied by
> value)
>        • Full userdata is no longer copied as light userdata between states;
> instead a new concept of "shared userdata" can be used to make userdata
> that is passable between states and who's lifespan can exceed that of the
> creating state.

>        • 'error' field for catching a lane's possible error

Looks like error field implicitly makes it possible to detect lane
lifetime status (say, by adding manual error() call in the end). Also
it looks weird to me. What is the intended usage scenario?

 local lane_h = f()
 if not lane_h.error then
     -- Nothing to guarantee lane did not crashed betwen previous line
and next one.
     local result = lane_h:unpack()
 else
     print("ERROR!")
 end

Am I right that unpack() (as well as indexing the handle) on error in
lane would throw the same error into the main state? If so, then it is
probably better to resort to good old pcall() family and, possibly,
add lane status query a-la coroutine.status().

> The changes are BIG. Basically, only the underlying threading part (OS
> specific things) has remained fairly intact. Everything about the internals
> is redone, in good Lua remaking fashion (if you can live without it, throw
> it away).

Is the Lanes library (given apparent fiddling with debug library)
still compatible with LuaJIT?

> Lindas provide a wealth of opportunities for any kinds of inter-state
> communication needs. Now, what I'd very much like, is to have a group of
> interested people help me fine tune the API's, before we make this truly
> public. Having a go with Lanes in some real world projects would soon show
> places where the API is suboptimal, and needs for features that are not
> currently there.

Please forgive me this question, but is there at least one (real
world) project that use Lanes 2008?

> Like the first ones:
>
> Watchdog.  Will make a sample on how to do it, ms-level resolution but based
> on wall clock time, not CPU time.  I am not sure if there's a way to get
> per-thread CPU statistics, anyways.
>
> Limiting thread number.  No, you currently cannot, but it would be easy to
> make a limit. API suggestion, please? ;)

Maybe something along the lines of (unimaginative)

 lanes.{get,set}maxthreads(N)

Note that for me this feature is quite meaningless (see suggestion
about sandboxing below) without allowing more than one state per
thread (also see below).

> You can limit the libraries a lane has available. If you don't allow
> 'package' (require), a lane won't be able to spawn sublanes.  But that's
> probably not the optimal solution?

Suggestion about sandboxing:

I'd left whole sandboxing issue to the Lanes user. In this case user
would know better which level of isolation for his client code is
needed.

> More than one Lua state per OS thread.  Currently, they go 1:1, and you
> should use coroutines for multithreading within the one state.

While I'm the big fan of coroutines, they are not *always* the
solution -- due to cooperativity. If one coroutine in the state stuck
on some heavy task, the rest of them are stuck as well. *Sometimes* it
is crucial to avoid this situation by all means. See below.

> Reason for this is at least pending operations (Lindas), where I don't see a
> way to share the OS thread with multiple Lua executions. Please share ideas
> if you feel different.

I'm not familiar with the implementation, so can you please be more
specific about the nature of the problem? (But see below on coroutines
vs monolitic functions.)

> Then again, threads should be light-weight entities on any OS, and we can
> succesfully run up to 1000 threads or more (memory is the limit) already
> with older Lanes (2007).

Unfortunately, OS threads are not so light-weight. A piece of
subjective experience: Recently, due to performance considerations, we
had to switch from one thread per state scenario in our server to the
multiple states per thread with fixed thread amount. Each state
contain a heavy on network/cpu multiclient thing with average
lifetime, up to several hours. States do not communicate between each
other in that specific case.

Server began to choke on several hundred of OS threads. After the
switch, performance on the low number of states were roughly the same,
performance on high number of states improved greatly (no more
chockes).

So, back to the point.
I was thinking about policy-based thread/state proportion control. Policies:
-- 1 thread / 1 state
-- N (fixed) threads / many states
-- Many threads / N (fixed) states per thread
-- Possibly, even user-supplied lane spawn policy handler (even if it
would have to be written in C), which given some load stats would
decide, put new lane to an old thread, spawn a new thread or deny lane
spawn at all (maybe also to delay spawn until some thread resource
would be freed -- this probably means that policy handler would
operate on queue of lanes to spawn).

I guess I've just figured why I suggest multiple lanes per thread, and
you find it weird. In most of my own code which involves threads and
Lua states, thread updates state by sending it a tick (repeatedly
calling callback Lua function, until it tells to stop; this usually
involves tick function updating the coroutine tree). Your Lane is a
single monolitic function. (This is not necessary bad; from the side
of simplicity or Lua-centric approach, it is even better.)

Still, lane as a monolitic function prohibits one thread to many
states pattern (unless you're able to do something via debug hooks by
instruction count here -- which I'd probably consider to be a
dangerous hack anyway).

Note that in the case of many states per thread, becomes valid above
point about stuck coroutines. If one state is stuck, then the rest of
states in the same thread are stuck as well. Considering this it may
be better to break state to thread dependency (works only with
callback/coroutine-based lanes):

We have a (smart) priority queue of lane states. We have a pool of
threads (either dynamic or static; see policy handler above). Each
thread takes a lane from the top of queue, updates it and puts it
back.

While writing this text, it came to my mind, that maybe Lanes'
intended paradigm is too different from what I have in my head. So you
probably should discard above propositions on one thread vs. many
states etc.

Maybe some complex example on Lanes would help (even on older
version). Do you have any available?

* * *

Mentioned note on lanes.new "calling convention":

IMO, in a general purpose library written in Lua one should not use
currying as lanes.new does.

   func= lanes.new [libs_str | opt_tbl [...]] ( lane_func )

Pass optional arguments to the lanes.new directly:

   func = lanes.new(lane_func, [libs_str | opt_tbl [...]])

Currying and stimulation of braces omission in function calls are good
for declarative code, config files, data description languages in
general etc. For dynamic code like Lines API, I consider such things
to be harmful to comprehension by average programmer -- too byzantine
without purpose. In my experience, people tend to stumble on such code
and ask: "Is this valid Lua?! How can it ever work?"

Also: what would happen if I forget to do last function call with
lane_func, and then call the result? Would I get meaningful error
message?

 local f= lanes.new "base"
 local l1= f(1) --> ?!

Alexander.