Skip to content

Scheduler Redesign#1017

Merged
MarkKoz merged 24 commits into
masterfrom
feat/backend/800/scheduler-redesign
Jul 12, 2020
Merged

Scheduler Redesign#1017
MarkKoz merged 24 commits into
masterfrom
feat/backend/800/scheduler-redesign

Conversation

@MarkKoz
Copy link
Copy Markdown
Contributor

@MarkKoz MarkKoz commented Jun 21, 2020

Resolves #800

The Scheduler must be instantiated rather than inherited. The new API relies on a coroutine being passed to one of three functions:

  • schedule() - schedule a coroutine immediately as a Task
  • schedule_at() - schedule a coroutine to run at a given datetime.datetime
  • schedule_later() - schedule a coroutine to run after sleeping for a given number of seconds

The need to implement an abstract method has been done away with. The ability to pass an arbitrary coroutine makes the Scheduler inherently more flexible. I wanted some discussion (#800 (comment)) on the signatures of these functions but I am content with the design I chose for the time being.

__contains__ was implemented to support checking for scheduled tasks by ID using the in operator. With this new possibility, the ignore_missing parameter of cancel was made redundant and removed.

The Scheduler carries over the coroutine closing logic from HelpChannels, which was done to prevent unawaited coroutine warnings. A bug with it has also been fixed. Now all users of the Scheduler can take advantage of this.

I'm aware an exception is raised when a reminder is deleted. I believe this exception was always being raised; it just wasn't visible until now. It will be fixed by #835.

MarkKoz added 15 commits June 19, 2020 21:50
Each instance now requires a name to be specified, which will be used
as the suffix of the logger's name. This removes the need to manually
prepend every log message with the name.
This is a major change which simplifies the interface. It removes the
need to implement an abstract method, which means the class can now
be instantiated rather than subclassed.
Makes them easier to identify when debugging.
The ability to use the `in` operator makes this obsolete. Callers can
check themselves if a task exists before they try to cancel it.
It's redundant. After all, this scheduler cannot schedule anything else.
Naming it "task" is inaccurate because `create_task` accepts a coroutine
rather than a Task. What it does is wrap the coroutine in a Task.
@MarkKoz MarkKoz added t: feature New feature or request s: WIP Work In Progress a: backend Related to internal functionality and utilities (error_handler, logging, security, utils and core) p: 1 - high High Priority labels Jun 21, 2020
MarkKoz added 3 commits June 23, 2020 23:52
This prevents unawaited coroutine warnings.
The task is already popped from the dict, so there is no need to delete
it afterwards.
The coroutine may cancel the scheduled task, which would also trigger
the finally block. The coroutine isn't necessarily finished when it
cancels the task, so it shouldn't be closed in this case.
@sentry
Copy link
Copy Markdown

sentry Bot commented Jun 30, 2020

Sentry issue: BOT-65

@MarkKoz MarkKoz removed the s: WIP Work In Progress label Jun 30, 2020
@MarkKoz MarkKoz marked this pull request as ready for review June 30, 2020 02:27
@MarkKoz MarkKoz requested a review from a team as a code owner June 30, 2020 02:27
@MarkKoz MarkKoz requested review from jb3 and removed request for a team June 30, 2020 02:27
@MarkKoz MarkKoz requested a review from aeros June 30, 2020 02:27
@ghost ghost added the needs 2 approvals label Jun 30, 2020
Copy link
Copy Markdown
Contributor

@aeros aeros left a comment

Choose a reason for hiding this comment

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

I'll try to do a more in-depth look at it later, but here's a few comments for now:

Comment thread bot/utils/scheduling.py
Comment thread bot/utils/scheduling.py Outdated
Comment thread bot/utils/scheduling.py
MarkKoz added 3 commits June 30, 2020 18:41
Showing the task ID in the logs makes them distinguishable from logs for
other tasks.

The coroutine state is logged because it may come in handy while
debugging; the coroutine inspection check hasn't been proven yet in
production.
It'd fail to schedule the coroutine otherwise anyway. There is also the
potential to close the coroutine, which may be unexpected to see for a
coroutine that was already running (despite being documented).
@MarkKoz MarkKoz requested a review from aeros July 6, 2020 17:17
Copy link
Copy Markdown
Contributor

@aeros aeros left a comment

Choose a reason for hiding this comment

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

LGTM. I didn't get the chance to feature test this to ensure the scheduler still works 100% correctly after the changes (such as for help channels, mod tools, and reminders), but all of the main async logic looks correct. Nicely done, Mark.

The only additional thing I could think of would be some unit tests for the scheduler (I didn't see any in tests/bot/utils/, but I'm assuming some of the existing tests might use it). Without dedicated unit tests, a complete feature test would be particularly important to do prior to merging. I'll try to do so if I can set aside the time for it, but anyone else is welcome to in the meantime of course.

Comment thread bot/utils/scheduling.py
Comment on lines +69 to +71
delay = (time - datetime.utcnow()).total_seconds()
if delay > 0:
coroutine = self._await_later(delay, task_id, coroutine)
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.

Not at all necessary and functionally equivalent, but this would be a good reason to use the walrus operator:

Suggested change
delay = (time - datetime.utcnow()).total_seconds()
if delay > 0:
coroutine = self._await_later(delay, task_id, coroutine)
if (delay := (time - datetime.utcnow()).total_seconds()) > 0:
coroutine = self._await_later(delay, task_id, coroutine)

I find that it's a minor improvement to readability by clearly communicating to the reader that the variable is only used within the scope of a statement, and makes the code a bit less repetitive.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I disagree because it makes the expression far too complicated. I wouldn't mind it if the expression started out simpler, but it already had a set of parenthesis and two function calls which just turns into a blob of parenthesis at a glance.

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.

Yeah, I can see that it could exceed the threshold of "reasonable amount of parenthesis". Either way, it's fairly subjective and not a huge deal, which is why I approved of the PR without it.

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.

I prefer it the way it is - I don't think the walrus adds any readability here, rather the opposite.

@MarkKoz
Copy link
Copy Markdown
Contributor Author

MarkKoz commented Jul 9, 2020

Thanks for the review. I did test things again and everything still seems to be in order. I'm not keen on writing tests any more; I need to study more before I can feel confident in writing tests again.

Copy link
Copy Markdown
Contributor

@ks129 ks129 left a comment

Choose a reason for hiding this comment

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

Looks nice

@ghost ghost removed the needs 1 approval label Jul 12, 2020
Copy link
Copy Markdown
Contributor

@lemonsaurus lemonsaurus left a comment

Choose a reason for hiding this comment

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

Looks excellent to my eyes. Well documented, exceptional commit history, lots of logging and a clean solution.

Probably ready to merge.

Comment thread bot/utils/scheduling.py
Comment on lines +69 to +71
delay = (time - datetime.utcnow()).total_seconds()
if delay > 0:
coroutine = self._await_later(delay, task_id, coroutine)
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.

I prefer it the way it is - I don't think the walrus adds any readability here, rather the opposite.

Comment thread bot/utils/scheduling.py
self._log.debug(f"Explicitly closing the coroutine for #{task_id}.")
coroutine.close()
else:
self._log.debug(f"Finally block reached for #{task_id}; {state=}")
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.

nice use of the new = format specifier.

@MarkKoz MarkKoz merged commit c975104 into master Jul 12, 2020
@MarkKoz MarkKoz deleted the feat/backend/800/scheduler-redesign branch July 12, 2020 22:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

a: backend Related to internal functionality and utilities (error_handler, logging, security, utils and core) p: 1 - high High Priority t: feature New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Scheduler Redesign

4 participants