Repository: biolab/orange3

Issue:
Usage statistics block exiting Orange on certain networks
##### Orange version
Any after #3151

##### Expected behavior
Closing a workflow closes the windows immediately.

##### Actual behavior
The workflow window closes immediately on no connection or on a very good connection. In some cases, when my network is connected but unreachable in a certain way closing the window blocks.

##### Steps to reproduce the behavior
Open Orange, add a widget, close Orange on a network with automatic proxy configuration but with proxies not set manually in Orange, and wait. Happens both on OS X and Windows.

Closing works properly if system proxies are set manually, because these kind of proxies are autodetected.

I can also reproduce this bug if I connect to a proxy-less network that requires me to login in a browser before I can continue (and I do not login). 

##### Additional info (worksheets, data, screenshots, ...)
Currently, closing a workflow makes a HTTP request and then blocks until answered/errored/timeout (supposedly, I never waited long enough for it to timeout).

A short timeout would help, but even waiting a few seconds for a window to close is still bad design.

I propose a different implementation. When a workflow is closed, usage statistics are just saved (as happens now if there is no connection). Then, when Orange is restarted, the data is sent in the background, similarly to our version checks.


Discussion:

Resolving patch:
diff --git a/Orange/canvas/__main__.py b/Orange/canvas/__main__.py
--- a/Orange/canvas/__main__.py
+++ b/Orange/canvas/__main__.py
@@ -34,6 +34,7 @@
 from Orange.canvas.gui.splashscreen import SplashScreen
 from Orange.canvas.config import cache_dir
 from Orange.canvas import config
+from Orange.canvas.document.usagestatistics import UsageStatistics
 
 from Orange.canvas.registry import qt
 from Orange.canvas.registry import WidgetRegistry, set_global_registry
@@ -271,6 +272,20 @@ def compare_versions(latest):
         thread.start()
         return thread
 
+def send_usage_statistics():
+
+    class SendUsageStatistics(QThread):
+        def run(self):
+            try:
+                usage = UsageStatistics()
+                usage.send_statistics()
+            except Exception:  # pylint: disable=broad-except
+                # exceptions in threads would crash Orange
+                log.warning("Failed to send usage statistics.")
+
+    thread = SendUsageStatistics()
+    thread.start()
+    return thread
 
 def main(argv=None):
     if argv is None:
@@ -545,6 +560,7 @@ def show_message(message):
     # local references prevent destruction
     survey = show_survey()
     update_check = check_for_updates()
+    send_stat = send_usage_statistics()
 
     # Tee stdout and stderr into Output dock
     log_view = canvas_window.log_view()
@@ -577,6 +593,7 @@ def show_message(message):
     del canvas_window
     del survey
     del update_check
+    del send_stat
 
     app.processEvents()
     app.flush()
diff --git a/Orange/canvas/document/usagestatistics.py b/Orange/canvas/document/usagestatistics.py
--- a/Orange/canvas/document/usagestatistics.py
+++ b/Orange/canvas/document/usagestatistics.py
@@ -105,6 +105,24 @@ def log_node_added(self, widget_name, extended_widget=None):
     def set_node_type(self, addition_type):
         self.__node_addition_type = addition_type
 
+    def send_statistics(self):
+        if release and config.settings()["error-reporting/send-statistics"]:
+            if os.path.isfile(statistics_path):
+                with open(statistics_path) as f:
+                    data = json.load(f)
+                try:
+                    r = requests.post(server_url, files={'file': json.dumps(data)})
+                    if r.status_code != 200:
+                        log.warning("Error communicating with server while attempting to send "
+                                    "usage statistics.")
+                        return
+                    # success - wipe statistics file
+                    log.info("Usage statistics sent.")
+                    with open(statistics_path, 'w') as f:
+                        json.dump([], f)
+                except (ConnectionError, requests.exceptions.RequestException):
+                    log.warning("Connection error while attempting to send usage statistics.")
+
     def write_statistics(self):
         if not release:
             log.info("Not sending usage statistics (non-release version of Orange detected).")
@@ -134,24 +152,9 @@ def write_statistics(self):
 
         data.append(statistics)
 
-        def store_data(d):
-            with open(statistics_path, 'w') as f:
-                json.dump(d, f)
-
-        try:
-            r = requests.post(server_url, files={'file': json.dumps(data)})
-            if r.status_code != 200:
-                log.warning("Error communicating with server while attempting to send "
-                            "usage statistics.")
-                store_data(data)
-                return
-            # success - wipe statistics file
-            log.info("Usage statistics sent.")
-            with open(statistics_path, 'w') as f:
-                json.dump([], f)
-        except (ConnectionError, requests.exceptions.RequestException):
-            log.warning("Connection error while attempting to send usage statistics.")
-            store_data(data)
+        with open(statistics_path, 'w') as f:
+            json.dump(data, f)
+
 
     @staticmethod
     def set_last_search_query(query):


Test patch: