Skip to content

Make scenario finalizing more functional #65

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

Closed
wants to merge 5 commits into from
Closed

Make scenario finalizing more functional #65

wants to merge 5 commits into from

Conversation

kontsaki
Copy link
Contributor

Running a scenario now returns a finalizer handler
which can be used to wait until a timeout for each
running scenario to finish. The runner is not exposed
to the user anymore.

@goodboy would something like this make sense?

@@ -106,20 +106,21 @@ You can also launch multiple multi-UA scenarios concurrently using
non-blocking mode:

```python
scens = []
finalizers = []
Copy link
Member

Choose a reason for hiding this comment

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

Yah much better.

pysipp/launch.py Outdated
@@ -185,3 +180,14 @@ async def finalize(runner, agents, timeout):
raise SIPpFailure(msg)

return cmds2procs


def finalizer(finalize_coro, runner, agents):
Copy link
Member

Choose a reason for hiding this comment

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

So if we go with this I guess it's a breaking change right?
We might need a major version release in that case, which is cool, and probably not that cray since we already are cutting py2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you mean bumping to version 2.0 some weeks after version 1.0 was released? 😄

Copy link
Member

Choose a reason for hiding this comment

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

Hehe yeah kinda.

I have a feeling we're going to break the public API a lot with all the python 3 / async stuff.

I think anyone afraid of that should stick to the 0.1.0 (which i'm sure lots of peeps are using in production already) and then we can move forward making the api more functional and async in a 1.0

What you think?

Also so sorry for 👻 ing; it's been quite the week 😂

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ah i just realized the release version you did a while back was 0.1.0 not 1.0.0

Copy link
Member

Choose a reason for hiding this comment

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

finalize_coro here is what?
If it's an async function i don't think we should have _coro.
Also in trio in general there's a distance kept from referring to coroutine objects directly so I'm thinking we can do a better name here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

oh yes, i think we don't even need to pass the finalize there, so we can remove the argument altogether.

Copy link
Member

@goodboy goodboy Aug 4, 2021

Choose a reason for hiding this comment

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

Actually further, unless I'm missing something, I don't think this is going to work.

If you return the with_timeout closure then trio.run() isn't going to be invoked until the user calls finalizer() in which case the when block=False here:
https://github.com/SIPp/pysipp/pull/65/files#diff-2b80e3f62bb560133d08a5a07a74bc8eac811db3cdf2680136050b78f37d324dR161

there won't be any background code run.

I can't remember exactly if we're calling whatever run_all_agents() returns but if it does, we should change the name I think?

Copy link
Contributor Author

@kontsaki kontsaki Aug 4, 2021

Choose a reason for hiding this comment

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

I've removed the argument.
I'm not sure what you mean, Scenario runs TrioRunner, through run_all_agents, which uses open_process

Copy link
Member

Choose a reason for hiding this comment

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

I'm confused too 😂

I don't get how this finalizer() can work.
Why does it need an inner closure like this?

Isn't trio.run() already running at this point, and if so why are we calling it again?

Maybe i'm forgetting how things work.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we trio.run once to run the subprocesses then once again to wait them to end or until timeout.

Copy link
Member

@goodboy goodboy left a comment

Choose a reason for hiding this comment

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

The one thing I was going to question was the move to " over '.

I get that that's a common convention but I remembered recently reading the pydantic author saying he preferred the '.
pydantic/pydantic#595 (comment)

Also this SO shows that it shouldn't matter pep8 wise.

I'm wondering if I'm naturally inclined to just have to hold shift modifier less 😂

If you really want the double quotes though that's fine by me since I'm not actively working on this code base right now.

@goodboy
Copy link
Member

goodboy commented Jan 30, 2021

@kontsaki yeah I think I don't really have a problem with the quotes change so much but having that here has made it pretty hard to read the functional changes in this PR - lotsa noise in the diff.

If you want to pull the quoting out into a separate formatting PR I think that would suit the git history better as well 😸

@kontsaki
Copy link
Contributor Author

oh right, sorry, my editor auto formats with black on saving and black is pretty opinionated about formatting. I too tend to avoid hitting modifiers when i don't have too, but in this case i just use single quotes when developing and leave black do all the dirty work and in general i find double quotes easier to read. i will try to remake the PR with only the proper changes.

@goodboy
Copy link
Member

goodboy commented Jan 30, 2021

but in this case i just use single quotes when developing and leave black do all the dirty work and in general i find double quotes easier to read.

Yah as mentioned I'm totally fine with the double quotes and we can probably add black to CI if you want but for understanding the details of this PR it would be handy to not have all that here 😂

@kontsaki kontsaki closed this Feb 12, 2021
@goodboy
Copy link
Member

goodboy commented Feb 12, 2021

@kontsaki yeah just fire the smaller PR when ready and also the " change in a new one as well.

Also feel free to PR in black to CI if you want.

@kontsaki kontsaki reopened this Feb 13, 2021
@kontsaki
Copy link
Contributor Author

@goodboy i've replaced single quotes with double quotes, black does more formatting though, if this PR can't be merged as is i may come back at a later time when i have more time to remake everything.

@kontsaki
Copy link
Contributor Author

i think it would be easier if we format all the project with black #67

@goodboy
Copy link
Member

goodboy commented Feb 13, 2021

@kontsaki looks like you've still got the black formatting history here so I still can't really read the diff 😂

Is there any way you can adjust the history to show just functional changes by rebasing those commits out of your original branch and then making a PR with just those commits here?

It should be something like git rebase start_commit end_commit --onto drop_py27_more I think.

@goodboy
Copy link
Member

goodboy commented Feb 13, 2021

Ahh I guess if it's your editor though that's going to be a pain eh since these changes are probably embedded in the commits themselves...

Maybe I'll just give it another go 🏄🏼

@kontsaki
Copy link
Contributor Author

we can also just merge #67 into master and i will rebase so it will only show the related diff

@kontsaki
Copy link
Contributor Author

after merging #69 the diff will be clear

@kontsaki
Copy link
Contributor Author

hey @goodboy , any chance to merge this and #69 or should i close them?

@goodboy
Copy link
Member

goodboy commented Mar 21, 2021

@kontsaki yeah of course!

Sorry just been caught up with other stuff.

I've commented in #69

@kontsaki
Copy link
Contributor Author

ping :)

@goodboy
Copy link
Member

goodboy commented May 28, 2021

@kontsaki heh. unless GH is being weird for me, seems like that didn't help at all 😂 (as I was kind of expecting).

@goodboy
Copy link
Member

goodboy commented May 28, 2021

Yup so afaict this is exactly what I expected: we've now got a merge history (mess imo) and we aren't any closer to making it easy to review the functional changes here.

@kontsaki, i I think you should take this branch and rebase it onto drop_py27, work through all the conflicts again and then we can discuss.

@goodboy
Copy link
Member

goodboy commented May 28, 2021

@kontsaki so as mentioned in #55 please factor out these black commits from here by rebasing this branch onto that one since I presume we're still keeping that one as the target?

@kontsaki
Copy link
Contributor Author

hello, yes I will it a try!

Running a scenario now returns a finalizer handler
which can be used to wait until a timeout for each
running scenario to finish. The runner is not exposed
to the user anymore.
@kontsaki
Copy link
Contributor Author

kontsaki commented Aug 4, 2021

hey @goodboy i rebased like you've requested, this is clear now.

for scen in scens:
scen.finalize()
for finalizer in finalizers:
finalizer(timeout=10)
Copy link
Member

@goodboy goodboy Aug 4, 2021

Choose a reason for hiding this comment

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

I think we've talked about it in passing but, we might want to move to an explicit method name here. I'm not against .__call__() per say but it does seem a bit too implicit the more we've let this sit.

Definitely something for future work though, no need to address here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hello, are you talking about the ScenarioType having both run and __call__ methods?
the finalizer returned is just a function. Docs are incorrect here thought i will fix them, calling scen returns the finalizer

Copy link
Member

Choose a reason for hiding this comment

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

I'm talking about 2 things:

  • Scenario.run() I think should be the api we encourage using.
  • you dropping .finalize() as part of the runner api.

Right now you've made things arguably more indirected by returning a closure from Scenario.__call__() only when block=False. I wouldn't call this any more functional per say other then that you return a closure instead of the runner.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The user should have access to the finalizer only when block=False. It would make much more sense if the finalizer was a method on the Scenario instead of juggling between objects, but that wasn't the case in the existing implementation.

Copy link
Member

Choose a reason for hiding this comment

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

It would make much more sense if the finalizer was a method on the Scenario instead of juggling between objects, but that wasn't the case in the existing implementation.

I like this idea better then returning an anon closure personally.
Is there any reason we can't add this?

It also would tie multi-script-execution lifetime to the scenario object, and for example if one wants to get the results from the last scenario run you just call Scenario.results() or Scenario.finalize() or wtv we decide. This makes the scen a little more future-like which it kind of is given we have non-blocking run support?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This solution will change the existing API and be backwards incompatible btw.

We are trying to implement a sync interface to an async functionality.

We can make the Scenario an awaitable to simplify the interface, but this would require the user to run async tests. Or the Scenario can just be a context manager.

Copy link
Member

@goodboy goodboy Aug 9, 2021

Choose a reason for hiding this comment

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

This solution will change the existing API and be backwards incompatible btw.

Not if we make Scenario.__call__() a thing tho right? Also, right now we're documented as scen.finalize() as the api no? I'm not sure how we're breaking anything exactly?

We are trying to implement a sync interface to an async functionality.

Yes which is why this needs thorough thought and ideally more then just our opinions 😂

Scenario can just be a context manager.

This idea i don't mind. If we're going to offer a sync interface it may also make sense to consider spawning trio in another thread and then having the main thread make calls to it with trio.to_thread() and friends.

We should eventually be supporting async code for spawning and scenarios especially to support multi-script cases where a user wants to do cmd restarts, cancels, etc.

pysipp/launch.py Outdated
def finalizer(finalize_coro, runner, agents):
def with_timeout(timeout):
return trio.run(
partial(finalize_coro, timeout=timeout),
Copy link
Member

Choose a reason for hiding this comment

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

This partial also feels weird.
Maybe put the positional args inside it as well?

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's because trio.run passes only args and not keyword args but sure

return runner
return await finalize(runner, agents, timeout)
else:
return finalizer(runner, agents)
Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I just don't see how this is beneficial.

Instead of returning the runner now we're returning a closure that calls trio.run() and that closure seems mostly to be a function created solely for the purposes of having it be a function (aka an object with .__call__())

I actually find this somewhat hard to follow and not any more functional then the original method which does this same thing.

Copy link
Contributor Author

@kontsaki kontsaki Aug 4, 2021

Choose a reason for hiding this comment

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

What is the benefit of exposing an implementation detail like the runner to the user? The downside is that it leaves more space for the user to misuse the API.

@@ -178,3 +180,10 @@ async def finalize(runner, agents, timeout):
raise SIPpFailure(msg)

return cmds2procs


def finalizer(runner, agents):
Copy link
Member

@goodboy goodboy Aug 4, 2021

Choose a reason for hiding this comment

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

Yeah, I don't see how this gives anything more functional honestly.

Instead of a runner (which also defines .__call__() and thus is duck-typed like a function) you return a closure function (which could have just as easily with a partial(trio.run, partial(finalize, runner, agents, timeout=timeout)) which seems nearly to be a no-op indirection level).

What's the motivation?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

the API is cleaner and less error prone as the runner is not exposed to the user anymore which means fewer chances of reaching invalid states. Functional as in less object oriented.

Copy link
Member

Choose a reason for hiding this comment

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

the API is cleaner and less error prone as the runner is not exposed to the user anymore which means fewer chances of reaching invalid states.

Not sure how that's possible to reach invalid states but yeah I guess in theory?

If we don't have a runner and only a simple function there there's also no future option for adding cancellation support, which is kinda what trio is all about. I don't want to over-design now or anything but making this the api is yes maybe simpler (as much as .__call__() vs. .finalize() is) but it may be somewhat limiting down the road?

Functional as in less object oriented.

Because we aren't returning a runner?
Calling TrioRunner.finalize() vs. lambda.__call__() i'm not really seeing as being any more or less objecty in python but 🤷🏼. I still don't understand why you need this function block, why not just return the partial on trio.run() if you wanted this; heck it could be a lambda?

print("Finalizing all scenarios and collecting results")
for scen in scens:
scen.finalize()
Copy link
Member

Choose a reason for hiding this comment

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

Yeah not sure what you mean about "breaking the api", you're already doing that with this change afaict.. so you'll have to explain.

@kontsaki kontsaki closed this Sep 15, 2021
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.

2 participants