-
Notifications
You must be signed in to change notification settings - Fork 177
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support disposal of Lwt_pool.t elements #483
Conversation
3f3068e
to
43e0f33
Compare
This implementation of I'll hopefully have a chance early next week to update this PR with that improved functionality unless someone disagrees with the approach. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @hcarty! I for one wishes this change to be merged. Left a note on the docs though 🙂
src/core/lwt_pool.mli
Outdated
to the optional function [check] as [check element callback]. [check] | ||
must call [callback] exactly once with [true] if [element] is still valid | ||
and [false] otherwise. If [check] calls [callback false] then [dispose] | ||
will be run on [element]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit confused on when dispose
is called. At a glance, it seems that both validate
and check
will call dispose
. Will dispose
be called twice? If not, how is the order of calling done?
Also this:
If a call to {!use} fails with a pool element
As my comment in the issue, I think it's unclear why a use
would fail. Does "fail" equals "trying to use an invalid pool element" (due to validate
)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I believe "fail" means raising an exception or rejecting the 'b Lwt.t
promise that use
's callback returns. It should definitely be clarified in the comment, because I also am guessing about what it means.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, that actually makes sense. I think it's the whole validate
and invalid elements
on use
paragraph just above it that confused me, I thought they are still talking about the same (or at least related) thing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to tweak the wording at bit - does this make the use of each argument any clearer? Suggestions are welcome - the wording has been tricky to get right.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new docs are pretty great!
@bobbypriambodo Can you comment on this? What should |
Intuitively for me The way node-pool achieve this is to first call Or, another approach that I thought of is that to leave it as it is, and let the user bear the responsibility of making sure that the pool is definitely not used anymore before clearing. In case of a pool of DB connections that I have in mind, |
@bobbypriambodo thanks for your thoughts! I guess that goes back to the question of whether we want a minimalistic |
I agree with @bobbypriambodo - I think it's reasonable to expect I don't want to block usage of the pool on outstanding element use after a |
And suggestions for approaches are of course welcome! |
@hcarty really love the new doc! It significantly clear things up. Now I understand the differences between Another minor note, there's an inconsistency, you defined |
src/core/lwt_pool.mli
Outdated
Note that pools are not for keeping Lwt threads. Lwt threads are very cheap | ||
to create and are pure. It is neither desirable nor possible to reuse them. | ||
If you want to have a pool of {e system} threads, consider module | ||
Note that pools are not for keeping Lwt promises. Lwt promises are very |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this paragraph is a fortunate casualty of the change in terminology from threads to promises – as in, I doubt anyone would think of keeping a "promise pool" anyway :) I suggest deleting most of this paragraph, and leaving some modified version of the last sentence about using Lwt_preemptive
for a system thread pool.
src/core/lwt_pool.mli
Outdated
[Lwt_preemptive]. *) | ||
|
||
type 'a t | ||
(** Pools containing values of type ['a]. *) | ||
(** Pools containing elements of type ['a]. *) | ||
|
||
val create : | ||
int -> | ||
?check : ('a -> (bool -> unit) -> unit) -> | ||
?validate : ('a -> bool Lwt.t) -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Optional, but maybe we should also change the order so that ?validate
comes before ?check
? On each call to use
, ?validate
is called first, then ?check
if the promise returned by ?use
is rejected.
src/core/lwt_pool.mli
Outdated
to the optional function [check] as [check element callback]. [check] | ||
must call [callback] exactly once with [true] if [element] is still valid | ||
and [false] otherwise. If [check] calls [callback false] then [dispose] | ||
will be run on [element]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The new docs are pretty great!
Thanks for the additional comments. I should get to this later this week, along with a potentially better approach to |
When
|
I think the feedback on the doc comments have been addressed in the latest commit. I've also updated Finally, I changed the logic around |
As maintainer, changing semantics makes me feel extremely paranoid, but I think this is ok. It may affect element creation timing for some users, but given Lwt is already a concurrency library, I would imagine nobody is able to count on the timing anyway.
Sorry to nitpick language, but I have to do this as part of putting the thread terminology to rest. It's misleading to say that anything happens in a promise (compare "in a thread," which makes sense, but Lwt is no longer called a threading library exactly because that doesn't make sense for Lwt). The only things that can be said to be in a promise are either a list of callbacks (when pending) or a value or exception (when resolved). A more right thing to say here is something like "rather than paying it right after a promise returned by the callback of |
src/core/lwt_pool.ml
Outdated
@@ -36,6 +36,8 @@ type 'a t = { | |||
(* Validate old pool members. *) | |||
dispose : 'a -> unit Lwt.t; | |||
(* Dispose of a pool member. *) | |||
mutable cleared : bool ref; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The amount of indirection here is a bit sneaky, and the below syntax
p.cleared := true;
p.cleared <- ref false;
was mysterious to me when I first saw it. This
cleared = ref false;
also doesn't accurately reflect what's really going on. It's tempting to think that cleared
involves only one level of indirection when seeing that.
I'd suggest making this an "immutable" field of type bool ref ref
.
The docs look pretty good. For me, I'll wait on reviewing the code in detail, as the build is currently failing. I think it's just the order of the labeled arguments that needs updating in the implementation. |
I'm uncertain here - from a user's perspective it's |
Oh no! Sorry about that, I pushed too soon. I'll get a fix in. |
Quite true :)
In a very loose sense there is a context, but it's a quite different kind of context than what is associated with a thread. That context is exactly a list of callbacks only (for a pending promise), and the promise is not "responsible" for it – it just has a list. I want to exactly avoid the notion that Lwt promises are similar to threads, including having contexts similar in any way one would ordinarily expect, so I'd rather avoid referring to both as "contexts" as well, as it's a loose use of terminology that could be confusing. The point is, promises are not "containers" for sequences of instructions, not units of execution, not something that runs, not something that can be interrupted, nothing "happens" in them. They are just smart references, that can hold lists of callbacks. Something else (a real thread, the main thread of the Lwt program) is responsible for running those callbacks at the right time, that main thread has a genuine thread context, etc. Promises are just inert data objects that are useful for synchronization and thinking about concurrency, and for getting a real thread to run things in a nice order. |
Or, put another way, promises are similar to streams or events in FRP, except they are one-shot. They are some kind of objects used for factoring your concurrent program, and you can register functions on them to create some kind of promise "network." Of course, I can't write that in the Lwt introduction :p |
Working on that test failure now |
Sorry for the noise from that failing test! Latest changes:
|
Thanks @hcarty, @bobbypriambodo! Me and @hcarty decided to merge this, skipping a super-thorough nitpicky review for now, but pushing that review to an overhaul (as recently done with Any comments or reviews in this PR are still welcome, though! |
@hcarty, is it correct to say that this logic is still in the code after this PR? I do think I see it there.. |
Never mind, sorry. This old comment Lines 106 to 108 in 2b8e531
is just 100% misleading. |
@hcarty, @bobbypriambodo what do you think about creating elements on an as-needed basis only, i.e. never re-creating inside either I guess this would change the semantics even more, as the pool wouldn't try at all to maintain the number of elements it grew to. If there are five concurrent users, the pool will tend to stay around five elements in size. If the number of concurrent users decreases to two, and elements sometimes die in I don't know if there is some wisdom behind the current semantics. I was not able to find any documented anywhere. @bobbypriambodo, do you know what is done in popular pool implementations in other languages? |
This would also mean that if a pool element fails |
@aantron My preference is to only create on |
Agreed. As an example of weird behavior triggered by the current effort to maintain the element count, In the current code, if |
In practice, the above means that if |
Sorry but at the moment I have no idea, I was more of a user than implementer and most of the libs I used just worked so I haven't got the chance to dig into the impls. I can look it up though, and perhaps post it into the Lwt_pool overhaul issue. |
An initial attempt at solving #480. Also tried to catch occurrences of
thread
in the doc comments and replace them withpromise
.EDIT by @aantron: resolves #480.