Skip to content

fix(connection): wrap reconnect in a task#1103

Merged
jujubot merged 1 commit intojuju:mainfrom
gboutry:fix/async-reconnect
Sep 26, 2024
Merged

fix(connection): wrap reconnect in a task#1103
jujubot merged 1 commit intojuju:mainfrom
gboutry:fix/async-reconnect

Conversation

@gboutry
Copy link
Copy Markdown
Contributor

@gboutry gboutry commented Sep 18, 2024

Description

Wrap reconnect coroutine in a task. This is due to a breaking change in python stdlib 3.11. Before 3.11 it only emitted a warning.

Fixes: #1102

All places calling asyncio.wait are already wrapping into a task:

done, pending = await jasyncio.wait([waiter, self._watcher_task],

done, pending = await jasyncio.wait([task] + event_tasks,

@jujubot
Copy link
Copy Markdown
Contributor

jujubot commented Sep 18, 2024

Thanks for opening a pull request! Please follow the instructions here to ensure your pull request is ready for review. Then, a maintainer will review your patch.

@hpidcock @anvial

1 similar comment
@jujubot
Copy link
Copy Markdown
Contributor

jujubot commented Sep 18, 2024

Thanks for opening a pull request! Please follow the instructions here to ensure your pull request is ready for review. Then, a maintainer will review your patch.

@hpidcock @anvial

@gboutry gboutry force-pushed the fix/async-reconnect branch from 2c0b3be to 742fd6c Compare September 18, 2024 11:59
It it forbidden to pass a coroutine to `asyncio.wait` in python3.11+.

Closes-Bug: juju#1102
Signed-off-by: Guillaume Boutry <guillaume.boutry@canonical.com>
@james-garner-canonical
Copy link
Copy Markdown
Contributor

/build

Copy link
Copy Markdown
Contributor

@james-garner-canonical james-garner-canonical left a comment

Choose a reason for hiding this comment

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

I'm not an expert here but this looks good to me. As noted, all the other calls to wait already use tasks. Likewise, all the other calls to reconnect are wrapped (in ensure_future), so this seems like an appropriate solution here.

# be cancelled when the pinger is cancelled by the reconnect,
# and we don't want the reconnect to be aborted halfway through
await jasyncio.wait([self.reconnect()])
await jasyncio.wait([jasyncio.create_task(self.reconnect())])
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

How is this different from just await self.reconnect()? It looks like asyncio.wait() is supposed to be used when you're waiting for multiple concurrent tasks. I'm not at all up on asyncio -- maybe @dimaqq could chime in too?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

True, I just assumed that asyncio was being used for a good reason, but I'm not super familiar with it either. Perhaps related to the comment above this line?

# the reconnect has to be done in a separate task because,
# if it is triggered by the pinger, then this RPC call will
# be cancelled when the pinger is cancelled by the reconnect,
# and we don't want the reconnect to be aborted halfway through

Copy link
Copy Markdown
Contributor Author

@gboutry gboutry Sep 24, 2024

Choose a reason for hiding this comment

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

This comment and call have been introduced in https://github.com/juju/python-libjuju/pull/148/files.

From the comment, we can infer that the caller wants the coroutine reconnect to be continue even if the rpc method is itself cancelled.

One of the side effect of the previous implementation of asyncio.wait was shielding the coroutine from the caller cancellation.

To make this behaviour clearer, we could instead:

await jasyncio.shield(jasyncio.create_task(self.reconnect()))

The shield would still yield a CancelledError, but the task inside would continue in the background.

Copy link
Copy Markdown
Contributor

@dimaqq dimaqq Sep 26, 2024

Choose a reason for hiding this comment

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

Given that there's a single item in the wait list, wdyt about this instead?

Suggested change
await jasyncio.wait([jasyncio.create_task(self.reconnect())])
await self.reconnect()

Maybe with a touch-up of the comment above, clarifying that cancellation mid-reconnect is an outstanding issue.

@dimaqq
Copy link
Copy Markdown
Contributor

dimaqq commented Sep 26, 2024

My 2c:

cancellation safety is a noble goal, but it won't be achieved by shielding this one task alone.

I would propose to keep this PR simple: only wrap coro in a task, which I believe, if a non-breaking change, as in it preserves current behaviour, good or bad.

Let's open a separate issue about cancellation.

Copy link
Copy Markdown
Contributor

@dimaqq dimaqq left a comment

Choose a reason for hiding this comment

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

Either fix will move us forward.

# be cancelled when the pinger is cancelled by the reconnect,
# and we don't want the reconnect to be aborted halfway through
await jasyncio.wait([self.reconnect()])
await jasyncio.wait([jasyncio.create_task(self.reconnect())])
Copy link
Copy Markdown
Contributor

@dimaqq dimaqq Sep 26, 2024

Choose a reason for hiding this comment

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

Given that there's a single item in the wait list, wdyt about this instead?

Suggested change
await jasyncio.wait([jasyncio.create_task(self.reconnect())])
await self.reconnect()

Maybe with a touch-up of the comment above, clarifying that cancellation mid-reconnect is an outstanding issue.

@gboutry
Copy link
Copy Markdown
Contributor Author

gboutry commented Sep 26, 2024

I would rather keep the current fix. It'll be the same as the 2 others places this is called with just the goal of unblocking our functional tests on Noble.

An other PR can come later reworking this more broadly in the library.

@james-garner-canonical
Copy link
Copy Markdown
Contributor

/build

@benhoyt
Copy link
Copy Markdown
Collaborator

benhoyt commented Sep 26, 2024

/merge

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.

pylibjuju can't reconnect on python 3.12

5 participants