This was 2 errors in one.
First problem: anaconda freeze for some time. Second problem: libiscsi was run on thread and libiscsi the library is not thread safe.
Anaconda freeze is because of GIL is taken on library thread which is trying to connect to server. The thread safe problem is because libiscsi is using signals internally which were delivered to another thread (this is hard to solve in python threads...). Solution is to start discover method on new process.
So new process is now created to call libiscsi discover_sendtargets method. This should fix both problems.
Note: Will be applied to master too.
From: Jiri Konecny jkonecny@redhat.com
This was 2 errors in one.
First problem: anaconda freeze for some time. Second problem: libiscsi was run on thread and libiscsi the library is not thread safe.
Anaconda freeze is because of GIL is taken on library thread which is trying to connect to server. The thread safe problem is because libiscsi is using signals internally which were delivered to another thread (this is hard to solve in python threads...). Solution is to start discover method on new process.
So new process is now created to call libiscsi discover_sendtargets method. This should fix both problems. --- blivet/iscsi.py | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 85 insertions(+), 5 deletions(-)
diff --git a/blivet/iscsi.py b/blivet/iscsi.py index c8c4e5d..74a2abd 100644 --- a/blivet/iscsi.py +++ b/blivet/iscsi.py @@ -23,6 +23,7 @@ from .flags import flags from .i18n import _ from .storage_log import log_exception_info +from multiprocessing import Process, Pipe import os import logging import shutil @@ -57,6 +58,60 @@ def has_iscsi():
return True
+ +def _call_discover_targets(conn_pipe, ipaddr, port, authinfo): + """ Function to separate iscsi :py:func:`libiscsi.discover_sendtargets` call to it's own process. + + The call must be started on special process because the :py:mod:`libiscsi` + library is not thread safe. When thread is used or it's called on + main thread the main thread will freeze. + + Also the discover is about four times slower (only for timeout) + this is caused by signals which are delivered to bad (python) + thread instead of C library thread. + + .. note:: + + To transfer data to main process ``conn_pipe`` (write only) is used. + Pipe returns tuple (ok, data). + + * ``ok``: True if everything was ok + * ``data``: Dictionary with :py:func:`libiscsi.node` parameters if ``ok`` was True. + Exception if ``ok`` was False + + :param conn_pipe: Pipe to the main process (write only) + :type conn_pipe: :py:func:`multiprocessing.Pipe` + :param str ipaddr: target IP address + :param str port: target port + :param authinfo: CHAP authentication data for node login + :type authinfo: Object returned by :py:func:`libiscsi.chapAuthInfo` or None + """ + try: + found_nodes = libiscsi.discover_sendtargets(address=ipaddr, + port=int(port), + authinfo=authinfo) + if found_nodes is None: + found_nodes = [] + + except IOError as ex: + conn_pipe.send((False, ex)) + conn_pipe.close() + return + + nodes = [] + + # the node object is not pickable so it can't be send with pipe + # TODO: change libiscsi.node to pickable object + for node in found_nodes: + nodes.append({'name': node.name, + 'tpgt': node.tpgt, + 'address': node.address, + 'port': node.port, + 'iface': node.iface}) + + conn_pipe.send((True, nodes)) + + class iscsi(object): """ iSCSI utility class.
@@ -277,12 +332,37 @@ def discover(self, ipaddr, port="3260", username=None, password=None, reverse_password=r_password) self.startup()
- # Note may raise an IOError - found_nodes = libiscsi.discover_sendtargets(address=ipaddr, - port=int(port), - authinfo=authinfo) - if found_nodes is None: + # start libiscsi discover_sendtargets in a new process + # threads can't be used here because the libiscsi library + # using signals internally which are send to bad thread + (con_recv, con_write) = Pipe(False) + p = Process(target=_call_discover_targets, args=(con_write, + ipaddr, + port, + authinfo, )) + p.start() + + try: + (ok, data) = con_recv.recv() + if not ok: + log.debug("iSCSI: exception raised when " + "discover_sendtargets process called: %s", + str(data)) + except EOFError: + ok = False + log.error("iSCSI: can't receive response from " + "_call_discover_targets") + + p.join() + + # convert dictionary back to iscsi nodes object + if ok is True: + found_nodes = [] + for node in data: + found_nodes.append(libiscsi.node(**node)) + else: return [] + self.discovered_targets[(ipaddr, port)] = [] for node in found_nodes: self.discovered_targets[(ipaddr, port)].append([node, False])
I did some changes like you said bcl. Please look on it now.
Thank you.
@@ -277,12 +332,37 @@ def discover(self, ipaddr, port="3260", username=None, password=None, reverse_password=r_password) self.startup()
# Note may raise an IOError
found_nodes = libiscsi.discover_sendtargets(address=ipaddr,
port=int(port),
authinfo=authinfo)
if found_nodes is None:
# start libiscsi discover_sendtargets in a new process
# threads can't be used here because the libiscsi library
# using signals internally which are send to bad thread
(con_recv, con_write) = Pipe(False)
p = Process(target=_call_discover_targets, args=(con_write,
ipaddr,
port,
authinfo, ))
p.start()
try:
(ok, data) = con_recv.recv()
if not ok:
log.debug("iSCSI: exception raised when "
"discover_sendtargets process called: %s",
str(data))
except EOFError:
ok = False
log.error("iSCSI: can't receive response from "
"_call_discover_targets")
p.join()
# convert dictionary back to iscsi nodes object
if ok is True:
``if ok`` would be sufficient here
Other than the neatpick above this looks good to me.
Added label: ACK.
@@ -277,12 +332,37 @@ def discover(self, ipaddr, port="3260", username=None, password=None, reverse_password=r_password) self.startup()
# Note may raise an IOError
found_nodes = libiscsi.discover_sendtargets(address=ipaddr,
port=int(port),
authinfo=authinfo)
if found_nodes is None:
# start libiscsi discover_sendtargets in a new process
# threads can't be used here because the libiscsi library
# using signals internally which are send to bad thread
(con_recv, con_write) = Pipe(False)
p = Process(target=_call_discover_targets, args=(con_write,
ipaddr,
port,
authinfo, ))
p.start()
try:
(ok, data) = con_recv.recv()
if not ok:
log.debug("iSCSI: exception raised when "
"discover_sendtargets process called: %s",
str(data))
except EOFError:
ok = False
log.error("iSCSI: can't receive response from "
"_call_discover_targets")
p.join()
# convert dictionary back to iscsi nodes object
if ok is True:
I will change it before push
Closed.
anaconda-patches@lists.fedorahosted.org