Skip to content

fix: allow empty yields#349

Closed
dhaigh wants to merge 1 commit intogoogleapis:masterfrom
dhaigh:fix-tasklet-empty-yields
Closed

fix: allow empty yields#349
dhaigh wants to merge 1 commit intogoogleapis:masterfrom
dhaigh:fix-tasklet-empty-yields

Conversation

@dhaigh
Copy link

@dhaigh dhaigh commented Feb 28, 2020

This fixes an issue we've found in the process of migrating our backend codebase from appengine ndb to python ndb: Tasklet yields on empty tuples/lists do not work.

Say you have code that looks like:

things = yield [bla.get_async() for bla in blahs]

This doesn't work if blahs == []. NDB hangs indefinitely because there are no futures to finish and tell the event loop to stop.

This issue also affects yields on multiple expressions. Something like this fails too (when blahs == []):

@ndb.synctasklet
def do_things_a_b(ids):
    (a, b) = yield \
        a_future, \
        [foo(id).get_async() for id in ids]

    return do_things(a, b)

# somewhere else
do_things_a_b([])

This latter example does not hang indefinitely but crashes immediately on this line:

AttributeError: 'list' object has no attribute 'add_done_callback'

@googlebot googlebot added the cla: yes This human has signed the Contributor License Agreement. label Feb 28, 2020
@chrisrossi
Copy link
Contributor

chrisrossi commented Mar 3, 2020

Hi, thank you for bringing this to our attention!

I've created issue #353 and proposed my own fix with #354 .

These address the first issue you brought up. As far as the second issue, I really wouldn't expect it to work the way you describe. I think it's perfectly reasonable to expect the arguments of the yield to be a tuple or list of futures. I don't think it's necessary to have NDB traverse an arbitrary tree of sequences looking for futures. I would suggest, in your second case, just rewriting your code:

@ndb.synctasklet
def do_things_a_b(ids):
    (a, b) = yield \
        [a_future] + \           # <--- This line changed
        [foo(id).get_async() for id in ids]

    return do_things(a, b)

# somewhere else
do_things_a_b([])

@dhaigh
Copy link
Author

dhaigh commented Mar 3, 2020

Hi Chris, thank you so much for the response and the PR!!

In relation to the multiple yields, my apologies for the bad example. Here is the some actual code in our codebase (that currently works when project_ids is []):

(all_projects, all_shared) = yield \
    ndb.get_multi_async(keys=[Project(id=project_id, parent=account.key).key for project_id in project_ids]), \
    [lookup_by_project_id_async(account, project_id) for project_id in project_ids]

I don't think your solution to concat the lists would work here since project_ids is of arbitrary length, but we are only unpacking two values.

I definitely agree that my (recursive) solution was kind of lame in that it handled arbitrarily nested lists/tuples, which there is probably no legitimate use case for.

Nonetheless I was hoping we would be able to achieve backwards compatibility?

chrisrossi pushed a commit to chrisrossi/python-ndb that referenced this pull request Mar 4, 2020
This adds backwards compatibility with the legacy code.

Fixes googleapis#349.
@chrisrossi
Copy link
Contributor

See #357, #358

@dhaigh
Copy link
Author

dhaigh commented Mar 4, 2020

brilliant!! thank you so much. these PRs fix both of my issues.

@dhaigh dhaigh closed this Mar 4, 2020
chrisrossi pushed a commit that referenced this pull request Mar 5, 2020
This adds backwards compatibility with the legacy code.

Fixes #349.
@dhaigh dhaigh deleted the fix-tasklet-empty-yields branch May 30, 2021 23:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cla: yes This human has signed the Contributor License Agreement.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants