-
Notifications
You must be signed in to change notification settings - Fork 332
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
Python: Add 'factory' support #1336
Merged
ac000
merged 2 commits into
nginx:master
from
gourav-kandoria:factory_support_in_python_config
Jul 2, 2024
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
def wsgi_a(env, start_response): | ||
start_response("200", [("Content-Length", "1")]) | ||
return [b"1"] | ||
|
||
|
||
def wsgi_b(env, start_response): | ||
start_response("200", [("Content-Length", "1")]) | ||
return [b"2"] | ||
|
||
|
||
def wsgi_a_factory(): | ||
return wsgi_a | ||
|
||
|
||
def wsgi_b_factory(): | ||
return wsgi_b | ||
|
||
|
||
wsgi_invalid_callable = None | ||
|
||
|
||
def wsgi_factory_returning_invalid_callable(): | ||
return wsgi_invalid_callable |
andrey-zelenkov marked this conversation as resolved.
Show resolved
Hide resolved
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
from unit.applications.lang.python import ApplicationPython | ||
from unit.option import option | ||
|
||
prerequisites = {"modules": {"python": "all"}} | ||
|
||
client = ApplicationPython() | ||
|
||
|
||
def test_python_factory_targets(): | ||
python_dir = f"{option.test_dir}/python" | ||
|
||
assert "success" in client.conf( | ||
{ | ||
"listeners": { | ||
"*:8080": {"pass": "applications/targets/1"}, | ||
"*:8081": {"pass": "applications/targets/2"}, | ||
"*:8082": {"pass": "applications/targets/factory-1"}, | ||
"*:8083": {"pass": "applications/targets/factory-2"}, | ||
}, | ||
"applications": { | ||
"targets": { | ||
"type": client.get_application_type(), | ||
"working_directory": f"{python_dir}/factory/", | ||
"path": f"{python_dir}/factory/", | ||
"targets": { | ||
"1": { | ||
"module": "wsgi", | ||
"callable": "wsgi_a", | ||
"factory": False, | ||
}, | ||
"2": { | ||
"module": "wsgi", | ||
"callable": "wsgi_b", | ||
"factory": False, | ||
}, | ||
"factory-1": { | ||
"module": "wsgi", | ||
"callable": "wsgi_a_factory", | ||
"factory": True, | ||
}, | ||
"factory-2": { | ||
"module": "wsgi", | ||
"callable": "wsgi_b_factory", | ||
"factory": True, | ||
}, | ||
}, | ||
} | ||
}, | ||
} | ||
) | ||
|
||
resp = client.get(port=8080) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "1" | ||
|
||
resp = client.get(port=8081) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "2" | ||
|
||
resp = client.get(port=8082) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "1" | ||
|
||
resp = client.get(port=8083) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "2" | ||
|
||
|
||
def test_python_factory_without_targets(): | ||
python_dir = f"{option.test_dir}/python" | ||
|
||
assert "success" in client.conf( | ||
{ | ||
"listeners": { | ||
"*:8080": {"pass": "applications/python-app-factory"}, | ||
"*:8081": {"pass": "applications/python-app"}, | ||
}, | ||
"applications": { | ||
"python-app-factory": { | ||
"type": client.get_application_type(), | ||
"working_directory": f"{python_dir}/factory/", | ||
"path": f"{python_dir}/factory/", | ||
"module": "wsgi", | ||
"callable": "wsgi_a_factory", | ||
"factory": True, | ||
}, | ||
"python-app": { | ||
"type": client.get_application_type(), | ||
"working_directory": f"{python_dir}/factory/", | ||
"path": f"{python_dir}/factory/", | ||
"module": "wsgi", | ||
"callable": "wsgi_b", | ||
"factory": False, | ||
}, | ||
}, | ||
} | ||
) | ||
|
||
resp = client.get(port=8080) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "1" | ||
|
||
resp = client.get(port=8081) | ||
assert resp["status"] == 200 | ||
assert resp["body"] == "2" | ||
|
||
|
||
def test_python_factory_invalid_callable_value(skip_alert): | ||
skip_alert( | ||
r"failed to apply new conf", | ||
r"did not return callable object", | ||
r"can not be called to fetch callable", | ||
) | ||
python_dir = f"{option.test_dir}/python" | ||
|
||
invalid_callable_values = [ | ||
"wsgi_factory_returning_invalid_callable", | ||
"wsgi_invalid_callable", | ||
] | ||
|
||
for callable_value in invalid_callable_values: | ||
assert "error" in client.conf( | ||
{ | ||
"listeners": {"*:8080": {"pass": "applications/targets/1"}}, | ||
"applications": { | ||
"targets": { | ||
"type": client.get_application_type(), | ||
"working_directory": f"{python_dir}/factory/", | ||
"path": f"{python_dir}/factory/", | ||
"targets": { | ||
"1": { | ||
"module": "wsgi", | ||
"callable": callable_value, | ||
"factory": True, | ||
}, | ||
}, | ||
} | ||
}, | ||
} | ||
) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like default value assigning can be optimised by using else branch for
if (value != NULL) {
condition later. It avoids reassigning in case whenvalue != NULL
is true.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That test is just to see if the factory variable is set. It may be set to
false
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you mean something like
Aside from adding an extra level of indentation. If
is_factory
isfalse
we still need to do thenxt_slow_path(PyCallable_Check(obj) == 0
check...We also want to distinguish between a
factory
failure vs an ordinarycallable
failure and without duplicating the abovecallable
check.If on the other hand you mean something like (untested)
It's not terrible...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Author has already added what I suggested:
Thanks @gourav-kandoria
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not a fan of
and I wouldn't want to see that kind of thing spread, but I could live with it in this case...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, there is no way that is an improvement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The thing is I explicitly asked for it to be done the way it was to avoid doing it the above way which just introduces unnecessary code.
You mention about optimisations. I'm curious what use case you're optimising for?
This is not a hot path.
Even then I doubt the above is an optimisation as it introduces more code, which generates more assembly and will likely have a larger i-cache footprint.
To illustrate take the following exampe
This produces the following assembly
You can see thw two
movl
instructions initialising the variables.We have one compare and jump.
Now lets look at what happens when we make it a if/then/else
That produces the following assembly
Yes, there is only one
movl
instruction at the start, but we now have two jumps.In the common case that I would suggest is that
factory
is not set,is_factory
is set to 0 (once) in both cases.But again this is all kind of moot, as this is not a hot path and cleaner code wins.
@gourav-kandoria
I would strongly suggest reverting this change...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Then it make sense to optimise it using
nxt_slow_path()
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By that I mean
nxt_python_set_target()
as a whole, it's only called at startup and maybe reconfigure time, not thousands or even hundreds or tens of times a second.We could, I'm not sue what we're optimising for here though.
likely/unlikely should really only be used in cases where you know for certain it's one way or the other a majority of time in all cases/workloads.
We seem to sprinkle these things around willy nilly.
Error branches are perhaps still a valid use... but again you really need to measure/profile this stuff...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ac000 sure, can revert it. BTW good to see this type of discussion. gave me some perspective about how to think about things at this level. just before reverting. @andrey-zelenkov are you also okay with @ac000 viewpoint. would wait for your reply before making any change as I would like to make only one and final change now 😅 😅