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

Problem: poller can segfault when modified from registered handler. #219

Merged
merged 2 commits into from
May 3, 2018

Conversation

kurdybacha
Copy link
Contributor

@kurdybacha kurdybacha commented May 3, 2018

It is possible that a user would like to add/remove sockets from
handlers. As handlers and poll items might be removed while not
being processed yet - we have a segfault situation.
Provided unit test remove_from_handler demonstrates the problem.

Solution: Modify internal poll item data structure only after processing
of events is finished.

Please note that events processing path performance remains the same when there are
no modification (add/remove) to the poller (no rebuild) - main use case.

As an effect of changes size method has been removed as it does not
represent any meaningful information anymore. There are active and pending
(waiting for rebuild) poll items so two different sizes. User can
easily track on their side number of registered sockets if original size
information is needed.

wait method returns number of processed sockets now. It might be
useful information to a user for no extra cost.

It is possible that a user would like to add/remove sockets from
handlers. As handlers and poll items might be removed while not
being processed yet - we have a segfault situation.
Provided unit test `remove_from_handler` demonstrates the problem.

Solution: Modify internal poll item data structure only after processing
of events is finished.

Please not that events processing path performance remains the same when there are
no modification (add/remove) to the poller (no rebuild) - main real use case.

As an effect of changes `size()` method has been removed as it does not
represent any meaningful information anymore. There are active and pending
(waiting for rebuild) poll items so two different sizes. User can
easily track on their side number of registered sockets if original size
information is needed.

`wait` method returns number of processed sockets now. It might be
useful information to a user for no extra cost.
@coveralls
Copy link

coveralls commented May 3, 2018

Pull Request Test Coverage Report for Build 69

  • 0 of 0 (NaN%) changed or added relevant lines in 0 files are covered.
  • 2 unchanged lines in 1 file lost coverage.
  • Overall coverage remained the same at 56.25%

Files with Coverage Reduction New Missed Lines %
zmq.hpp 2 64.38%
Totals Coverage Status
Change from base Build 67: 0.0%
Covered Lines: 36
Relevant Lines: 64

💛 - Coveralls

@bluca bluca merged commit faf6671 into zeromq:master May 3, 2018

throw error_t ();
}

size_t size ()
Copy link
Member

Choose a reason for hiding this comment

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

I don't get why removing this method is necessary. There are several other options: get the size before pending changes, get the size after pending changes, provide getters for both.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what value does it bring to the user? Would it not be more confusing than useful?
poller is not really a collection, you can not iterate through it etc. and user needs to store info about registered socket already in order to successfully unregistered it later.

Copy link
Member

Choose a reason for hiding this comment

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

Maybe the size is not a relevant information, but the information if the poller is empty or not is, since this affects how wait may be called the next time. Therefore, this information should take into account that pending changes are applied. (I am not sure how you implemented handling of pending changes, but I think that there can only be pending changes if empty is called during an active call of wait, right? Actually, such reentrant calls are not relevant in the use cases I had in mind previously).

Overall I would be fine with removing the size method if an empty method is added instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

..but empty has the same problem as size. For example:

zmq::poller_t poller;
poller.add (socket, ZMQ_POLLIN, [&](short events) {
    poller.remove (socket);
    bool empty = poller.empty();
    // hmmm
});

What would you expect empty to be here? You are in the poller's event processing loop but just has removed last socket. If you still insist on adding empty or size I would go for true in the example above as it reflects latest user's actions.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

just created a PR for that.

@sigiesec
Copy link
Member

sigiesec commented May 4, 2018

I am now quite sure that it makes sense to split poller_t into two layers: one that only manages socket registrations, and a separate one on top of that that knows about callback handlers.

@@ -9,7 +9,6 @@
TEST(poller, create_destroy)
{
zmq::poller_t poller;
ASSERT_EQ(0u, poller.size ());
Copy link
Member

Choose a reason for hiding this comment

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

Something should be asserted here.

a.reset ();
ASSERT_EQ(0u, b.size ());
Copy link
Member

Choose a reason for hiding this comment

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

There must be some assertion here. Or maybe the move operations of poller_t should be deleted entirely.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is perfectly valid that unit test does not assert and runs without a crash/exception but if maintainers decide otherwise I am fine with that.

Copy link
Member

Choose a reason for hiding this comment

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

I completely agree in principle, but I think in this case there should be some assertion.

This test would accept a move constructor that is a no-op...

But maybe it is fine if proper assertion are added for the non-empty case.

But still, I am not sure if it makes sense to provide move operations at all. If one wants to move a poller, they could use a unique_ptr to a poller_t instead.

Copy link
Contributor Author

@kurdybacha kurdybacha May 9, 2018

Choose a reason for hiding this comment

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

Everything inside the poller is movable already so it make it easy to keep poller movable too. Created a new PR that fixes moves operations and makes them compiler generated for us.

a.reset ();
ASSERT_EQ(1u, b.size ());
Copy link
Member

Choose a reason for hiding this comment

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

Something must be asserted here. If there are no query operations, wait should be called on both pollers to ensure that only b has an effect.

@kurdybacha kurdybacha deleted the poller_fix branch May 6, 2018 16:42
kurdybacha added a commit to kurdybacha/cppzmq that referenced this pull request May 9, 2018
As disscussed on zeromq#219 PR bringing back `size` method and adding `empty`
for completeness.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants