Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

In submit_iteration, don't send None values to the future #236

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,19 @@
Changelog for Traits Futures
============================

Release 0.3.0
-------------

Changes
~~~~~~~

* For an iteration submitted via ``submit_iteration``, a plain ``yield``
statement in the submitted iterator marks a cancellation point (as before)
but no longer sends a ``None`` value to the future. A ``yield expr``
statement with a non-``None`` value both marks a cancellation point and
sends a value to the future, as before.


Release 0.2.0
-------------

Expand Down
24 changes: 15 additions & 9 deletions docs/source/guide/cancel.rst
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,18 @@ to cancel mid-calculation. Those two changes are:
- insert a |yield| statement at possible interruption points
- submit the background task via |submit_iteration| instead of |submit_call|.

The implementation of |submit_iteration| not only checks for cancellation, but
also sends a message to the future at every |yield| point. For that reason, you
don't want to yield too often - as a guide, sending a message more than 100
times per second is likely be inefficient. But conversely, if you yield too
rarely, then the checks for cancellation will be spaced further apart, so you
increase the latency for a response to a cancellation request.
In the simplest case, plain |yield| statements are added; these simply mark
cancellation points. But the |yield| statement can also yield values: a |yield|
with a non-None value also marks a cancellation point, but in addition it sends
a message containing that value to the ``result_value`` trait of the foreground
future; this can be used to send progress information or partial results to the
future.

When yielding values, there are tradeoffs to consider when deciding how
frequently to yield. As a guide, sending a value more than 100 times per
second is likely be inefficient. But conversely, if you yield too rarely, then
the checks for cancellation will be spaced further apart, so you increase the
latency for a response to a cancellation request.

Making the approximation cancellable
------------------------------------
Expand Down Expand Up @@ -112,9 +118,9 @@ Sending partial results
-----------------------

As we mentioned above, |submit_iteration| also sends a message to the
|IterationFuture| whenever it encounters a |yield|. That message carries
whatever was yielded as a payload. That means that we can replace the plain
|yield| to yield an expression, providing information to the future. That
|IterationFuture| whenever it encounters a |yield| with a value. That message
carries whatever was yielded as a payload. That means that we can replace the
plain |yield| to yield an expression, providing information to the future. That
information could contain progress information, partial results, log messages,
or any useful information you want to provide (though ideally, whatever Python
object you yield should be both immutable and pickleable). Every time you do a
Expand Down
24 changes: 23 additions & 1 deletion traits_futures/background_iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,13 @@ def __call__(self, send, cancelled):
# exception carries that value. Return it.
return e.value

send(GENERATED, result)
# A plain yield without an argument should not send a message; it's
Copy link
Member Author

Choose a reason for hiding this comment

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

I think this comment needs to be rewritten, or possibly simply removed. The reference to yield doesn't make sense in the case that what's been submitted is a simple iterator rather than a generator created by a generator function.

Copy link
Contributor

Choose a reason for hiding this comment

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

You could potentially simply point to the submit_iteration function instead of repeating a portion/all of the docstring from that function here.

# used purely to mark a possible cancellation point. This also
# means that it's not possible to send `None` as an iteration
# result.
if result is not None:
send(GENERATED, result)

# Don't keep a reference around until the next iteration.
del result

Expand Down Expand Up @@ -123,6 +129,22 @@ def submit_iteration(executor, callable, *args, **kwargs):
"""
Convenience function to submit a background iteration to an executor.

The submitted callable should return an iterator when called with the given
positional and keyword arguments. The background task iterates through that
iterator, passing each non-None value produced by the iterator to the
corresponding future's ``result_event`` trait, and passing any final
result returned (via the value of the iterator's ``StopIteration``
exception) to the future's ``result`` property.

Note that any ``None`` value produced by the iterator will not be passed on
to the future. This allows the iterator to produce a ``None`` simply to
indicate a potential interruption point of the background calculation,
without incurring the overhead of passing a message to the main thread.

.. versionchanged:: 0.3.0
Non-None values simply mark possible cancellation points, and
are no longer passed to the future.

Parameters
----------
executor : TraitsExecutor
Expand Down
22 changes: 22 additions & 0 deletions traits_futures/tests/background_iteration_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,17 @@ def iteration_with_result():
return 45


def iteration_with_plain_yields():
"""
Iteration using plain yields to mark cancellation points.
"""
yield 17
yield
yield None # same as plain yield
yield 29
yield


class IterationFutureListener(HasStrictTraits):
#: The object we're listening to.
future = Instance(IterationFuture)
Expand Down Expand Up @@ -420,6 +431,17 @@ def test_iteration_with_result(self):
self.assertResult(future, 45)
self.assertNoException(future)

def test_plain_yields_dont_send_a_message(self):
future = submit_iteration(self.executor, iteration_with_plain_yields)
listener = IterationFutureListener(future=future)

self.wait_until_done(future)

self.assertEqual(listener.states, [WAITING, EXECUTING, COMPLETED])
self.assertEqual(listener.results, [17, 29])
self.assertResult(future, None)
self.assertNoException(future)

# Helper functions

def halt_executor(self):
Expand Down