Skip to content
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

fix #14: add windowPut--i.e. put with strategy based on num pending puts #17

Open
wants to merge 3 commits into
base: main
Choose a base branch
from

Conversation

rgrover
Copy link
Contributor

@rgrover rgrover commented May 25, 2019

please note that the window strategy is based on the number of pending puts. Also note that the DropHead strategy drops the head of the pending puts, but doesn't touch the current value of the AVar. In other words, if DropHead is used to achieve a sliding window, and if the first take/read is called after more than window-width number of puts, the effects of the very first put cannot be dropped/undone. The first put takes effect eagerly, and I have not changed that behaviour.

@CLAassistant
Copy link

CLAassistant commented May 25, 2019

CLA assistant check
All committers have signed the CLA.

@natefaubion
Copy link
Contributor

Thanks for working on this! I'll try to look at this sometime this week. Ping if that doesn't happen.

-- Drop the head, and push onto the tail
DropHead e -> do
void $ Fn.runFn3 _tailPuts ffiUtil e avar
Fn.runFn4 _snocVar ffiUtil value avar cb
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these operations be atomic? Right now tailPuts/initPuts invokes a callback, which may end up doing something that affects the internal AVar state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, perhaps there is a need to create atomic versions of these combinations where the callbacks corresponding to dropped entries are invoked after the avar has been updated. I'll submit an update.

It is still possible for the callbacks to invalidate the property being enforced by the strategy. Strategies can still be coded to recover from such deviations.

@rgrover
Copy link
Contributor Author

rgrover commented Jul 1, 2019

Could you please review the latest of this pull request? Thanks.

@@ -50,7 +64,59 @@ kill err avar = Fn.runFn3 _killVar ffiUtil err avar
-- | the AVar becomes available. Returns an effect which will remove the
-- | callback from the pending queue.
put ∷ ∀ a. a → AVar a → AVarCallback Unit → Effect (Effect Unit)
put value avar cb = Fn.runFn4 _putVar ffiUtil value avar cb
put = windowPut (const PushTail)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is by far the most common, maybe it makes sense to avoid the dispatch overhead and call snocVar directly.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would be less concerned about this if we didn't have an ADT.

| DropHead Error -- Drop the head, and push onto the tail
| DropTail Error -- Drop the tail, and push onto the head
| SwapHead Error -- Replace the head
| SwapTail Error -- Replace the tail
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is having an actual data type necessary? The upside is that you can pattern match and change the operations later (I don't have a use case for this). The downside is that there is a linear dispatching overhead in the windowPut interpreter. Can you think of a use case that takes advantage of a primitive ADT rather than just bundling the logic up in a closure for each strategy?

foreign import _tryPutVar ∷ ∀ a. Fn.Fn3 FFIUtil a (AVar a) (Effect Boolean)
foreign import _takeVar ∷ ∀ a. Fn.Fn3 FFIUtil (AVar a) (AVarCallback a) (Effect (Effect Unit))
foreign import _tryTakeVar ∷ ∀ a. Fn.Fn2 FFIUtil (AVar a) (Effect (Maybe a))
foreign import _readVar ∷ ∀ a. Fn.Fn3 FFIUtil (AVar a) (AVarCallback a) (Effect (Effect Unit))
foreign import _tryReadVar ∷ ∀ a. Fn.Fn2 FFIUtil (AVar a) (Effect (Maybe a))
foreign import _status ∷ ∀ a. Fn.Fn2 FFIUtil (AVar a) (Effect (AVarStatus a))
foreign import _pendingPuts :: ∀ a. AVar a -> Int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't a pure operation since an avar size is mutable. This can potentially cause issues depending on where and how the actual effects are interleaved.

mPut <- Fn.runFn2 _initPuts ffiUtil avar
canceller <- Fn.runFn4 _snocVar ffiUtil value avar cb
canceller <$ do
traverse_ <@> mPut $ \{cb: cb'} -> cb' (Left e)
Copy link
Contributor

@natefaubion natefaubion Jul 2, 2019

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we didn't have an ADT, then I think these could easily be implemented in the FFI and avoid the effect thunk and Maybe allocation. What do you think?

I'm not a big fan of the canceller <$ do traverse_ <@> pattern you are using. It took me a second to figure out what it's actually doing. I would suggest:

for_ mPut \{ cb: cb'} -> cb' (Left e)
pure canceller

This will optimize better.

eq "barfoo" <$> Ref.read ref

test_max2_put_take ∷ Effect Unit
test_max2_put_take = test "max2: put/take" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the motivation for this test that's different from max1?

eq "barfoo" <$> Ref.read ref

test_window2_put_take ∷ Effect Unit
test_window2_put_take = test "win2: put/take" do
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise with max2, it's not clear to me what this is testing vs the first one.

test_max1_put_put_put_take
test_window1_put_take
test_window2_put_take
test_window1_put_put_put_take_take
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general, it seems like we should test all the strategies on some level.

@natefaubion
Copy link
Contributor

I would like to get feedback from @kritzcreek and @garyb on what they think of this API, since they worked on concurrent-queues, and this aims to obviate some of that functionality.

@thomashoneyman thomashoneyman changed the base branch from master to main October 4, 2020 01:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants