-
Notifications
You must be signed in to change notification settings - Fork 157
fix: bind py4j callback server to a dynamic port to avoid 25334 collision (#86, #19) #275
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -452,6 +452,59 @@ def test_isUnique(self): | |
| self.assertEqual(self.isUnique("b", "All rows are unique"), [Row(constraint_status="Success")]) | ||
| self.assertEqual(self.isUnique("email", "All rows are unique"), [Row(constraint_status="Success")]) | ||
|
|
||
| def test_lambda_check_uses_dynamic_callback_port(self): | ||
| """Regression test for the py4j callback-server default-port (25334) collision. | ||
|
|
||
| Closes #86, #19, #7, #72, #156, #173, #198. | ||
|
|
||
| With older gateways PythonCallback let py4j bind the hardcoded default | ||
| callback port 25334. Concurrent or repeated runs on the same host that | ||
| used a lambda-based Check then collided with | ||
| ``OSError: [Errno 98] Address already in use (127.0.0.1:25334)``. The fix | ||
| forces a dynamic (OS-assigned) port by setting the gateway's existing | ||
| callback-server parameters to ``port = 0`` before starting the server the | ||
| stock way, so PySpark's callback wiring -- and clean shutdown -- are | ||
| preserved. | ||
|
|
||
| This test runs lambda-assertion Checks and asserts that the resulting | ||
| callback server is listening on a dynamic port -- never the hardcoded | ||
| default 25334. The second lambda assertion additionally exercises the #19 | ||
| path: reusing PySpark's parameters keeps the JVM callback client pointed | ||
| at the bound port, otherwise the check would fail with "Error while | ||
| obtaining a new communication channel". | ||
|
|
||
| This test is deliberately NON-INVASIVE: it does not start, stop, restart, | ||
| or close the shared py4j callback server, and it does not bind any port | ||
| itself. Manipulating the shared callback server here was found to either | ||
| hang ``tearDownClass``'s ``shutdown_callback_server`` (a callback thread | ||
| left blocked in ``recv`` never joins) or break later lambda tests that | ||
| reuse the connection. So we only observe the port the production fix chose. | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MISSING_TEST: The regression test is order-dependent and non-hermetic: it asserts the port is not 25334 but relies on an earlier test having triggered the fix. If the fix were reverted, the test would still pass whenever py4j's own default binding happened to differ, and it never independently forces the
Refutation trail (why this survived the Critic's disprove pass)Hypothesis (Investigator): Disprove attempt (Critic): The Critic's default verdict is OVERTURNED. UPHELD findings are those it tried — and failed — to refute. |
||
| """ | ||
| gateway = self.spark.sparkContext._gateway | ||
|
|
||
| # A lambda assertion ensures the py4j callback server is running (started by | ||
| # PythonCallback via the fix on an OS-assigned dynamic port). | ||
| result = self.hasSize(lambda x: x == 3.0) | ||
| self.assertEqual(result, [Row(constraint_status="Success")]) | ||
|
|
||
| callback_server = gateway.get_callback_server() | ||
| self.assertIsNotNone(callback_server, "a lambda Check should have started the callback server") | ||
| listening_port = callback_server.get_listening_port() | ||
| self.assertNotEqual( | ||
| listening_port, | ||
| 25334, | ||
| "Callback server must not use the hardcoded default port 25334; " | ||
| f"got {listening_port}", | ||
| ) | ||
| self.assertGreater(listening_port, 0, "callback server should be bound to a real port") | ||
|
|
||
| # A second lambda assertion in the same process exercises the #19 path | ||
| # (the JVM callback client must point at the dynamic port). If the client | ||
| # were not reset, this would raise "Error while obtaining a new | ||
| # communication channel" instead of returning a result. | ||
| result2 = self.hasSize(lambda x: x >= 2.0 and x < 5.0) | ||
| self.assertEqual(result2, [Row(constraint_status="Success")]) | ||
|
|
||
| def test_fail_isUnique(self): | ||
| self.assertEqual(self.isUnique("d"), [Row(constraint_status="Failure")]) | ||
| self.assertEqual(self.isUnique("f", "All rows are unique"), [Row(constraint_status="Failure")]) | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.