-
Notifications
You must be signed in to change notification settings - Fork 445
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
Find the running core port and use it in GUI #7162
Conversation
390e3e6
to
d3b1091
Compare
d534eca
to
4ea5f08
Compare
1345bca
to
69ac6d8
Compare
src/tribler/gui/core_manager.py
Outdated
@@ -118,6 +125,7 @@ def start_tribler_core(self): | |||
connect(self.core_process.finished, self.on_core_finished) | |||
self._logger.info(f'Start Tribler core process {sys.executable} with arguments: {core_args}') | |||
self.core_process.start(sys.executable, core_args) | |||
self.port_checker.setup_with_pid(self.core_process.processId()) |
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.
Unfortunately, this logic currently does not work on Windows, as self.core_process.processId()
returns an incorrect PID value, and then the port checker cannot find open ports for a core process.
According to this, the PID value can be incorrect if asked too quickly. You need to be sure that the process is actually started, so this logic should be moved to on_core_started
. I tried it, but it didn't help to fix the problem.
I suspect an incorrect PID value can result from a bug in the QProcess.processId()
implementation for Windows.
There is also a low-level method, self.core_process.pid()
; I also tried it. The result is OS-dependent; on Unix, it is a normal PID value, whereas, on Windows, it is an instance of a sip.voidptr
object. This voidptr
should point to a _PROCESS_INFORMATION
structure. In principle, extracting the PID value from this structure should be possible using ctypes
. I tried the following:
ptr_as_int = self.core_process.pid().__int__()
import ctypes
class _PROCESS_INFORMATION(ctypes.Structure):
_fields_ = [("hProcess", ctypes.c_int),
("hThread", ctypes.c_int),
("dwProcessID", ctypes.c_int),
("dwThreadID", ctypes.c_int)]
process_information = ctypes.cast(ctypes.c_void_p(ptr_as_int), ctypes.POINTER(_PROCESS_INFORMATION))
pid = process_information.dwProcessID
But the resulting PID value is still incorrect. Maybe I did something wrong with ctypes here.
Example values from a debug session:
- Actual GUI process PID: 32704
- Actual Core process PID: 34640
core_process.processId()
value: 31468core_process.pid()
value: <sip.voidptr object at 0x0000014984C63D20>core_process.pid().__int__()
value: 1415218422960process_information.dwProcessID
value: 2372process_information.hProcess
value: 2376
In conclusion, at this moment, I don't see any reliable way to get a correct PID value of the QProcess
instance on Windows. As a simple alternative, we can do one of the following:
- Extract the PID value from the
triblerd.lock
file usingProcessChecker
. I modified the code this way, and it works, so it looks like the easiest way to fix the issue. - Pass PID as a first stdout output of the Core process, and parse it in the GUI process.
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.
As tested the pid is working on all machines.
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.
As it turns out, the reason it does not work correctly on my PC is venv. Venv creates a separate python.exe
wrapper that calls the actual Python inside. So, the PID of the Core process that QProcess
returns is the PID of the venv wrapper and not of the actual Core process. As most Tribler users use the compiled version of Tribler, they should not experience this problem. Nevertheless, we should be aware of this venv behavior.
On my machine, the PR is working, but not in the way it may be expected. As the actual PID of the Core process is different from what QProcess
returns, the PortChecker cannot find the correct port, so it never calls the EventRequestManager.update_port
method. Instead, the timeout of EventRequestManager.connect_timer
forces the request with the default port value, which turns out to be the correct one.
12bcd30
to
d80d464
Compare
74d4873
to
af0b518
Compare
def check_port(self): | ||
self._logger.info(f"Checking ports; Base Port: {self.base_port}") | ||
self.detect_port_from_process() | ||
if self.detected_port and self._callback: |
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 checker timer will be restarted if the port is detected, but a callback is not set. So, if a callback is not specified, the PortChecker will continue work indefinitely, which looks like an incorrect behavior.
@@ -122,8 +125,20 @@ def start_tribler_core(self): | |||
def on_core_started(self): | |||
self.core_started = True | |||
self.core_running = True | |||
self.start_port_checker() |
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 think the CoreManager.stop()
method should stop the port checker as well
self.events_manager.connect(reschedule_on_err=True) # retry until REST API is ready | ||
|
||
def start_port_checker(self): |
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.
It may be a bit safer to check if self.port_checker is None
at the beginning of the method for a case when the method is called twice after some future refactoring.
self.events_manager.connect(reschedule_on_err=True) # retry until REST API is ready | ||
|
||
def start_port_checker(self): | ||
core_process_id = self.core_process.processId() |
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.
Can you add a comment for future developers that when using venv
on Windows, the PID value returned from the QProcess.processId
is incorrect, as it is the PID of the venv
wrapper process and not the PID of the Core process itself? Something like:
core_process_id = self.core_process.processId() | |
# When using venv on Windows, the Core process PID value returned by QProcess is incorrect, | |
# as it is the PID of the venv wrapper process and not the PID of the actual Core process. | |
# In that case, PortChecker finds nothing, and the default port is used. | |
core_process_id = self.core_process.processId() |
src/tribler/gui/core_manager.py
Outdated
@@ -118,6 +125,7 @@ def start_tribler_core(self): | |||
connect(self.core_process.finished, self.on_core_finished) | |||
self._logger.info(f'Start Tribler core process {sys.executable} with arguments: {core_args}') | |||
self.core_process.start(sys.executable, core_args) | |||
self.port_checker.setup_with_pid(self.core_process.processId()) |
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.
As it turns out, the reason it does not work correctly on my PC is venv. Venv creates a separate python.exe
wrapper that calls the actual Python inside. So, the PID of the Core process that QProcess
returns is the PID of the venv wrapper and not of the actual Core process. As most Tribler users use the compiled version of Tribler, they should not experience this problem. Nevertheless, we should be aware of this venv behavior.
On my machine, the PR is working, but not in the way it may be expected. As the actual PID of the Core process is different from what QProcess
returns, the PortChecker cannot find the correct port, so it never calls the EventRequestManager.update_port
method. Instead, the timeout of EventRequestManager.connect_timer
forces the request with the default port value, which turns out to be the correct one.
Based on the ideas and code from this PR, PR #7251 was implemented with a different way of passing information about the opened port |
This PR adds a PortChecker class that finds the closest port opened by a process identified by given pid and the base port.
A callback can also be set which is triggered when the port is detected.
The port checker is used by the CoreManager to find the core API port.
When the port is detected, request manager and event manager are updated so they can operate with the correct port.
Further, it also adds two environment variables
TRIBLER_API_PORT_INITIAL
andTRIBLER_API_PORT_DETECTED
with values initial and detected api port values. This is so that these variables will be made available in sentry report if there is a crash.Fixes #7137