Repository: biolab/orange3

Issue:
Errors in settings migration block the workflow
##### Orange version
master

##### Expected behavior
If an error in setting or context migration happens (and it inevitably will, someday), a user should see our error window (and hopefully report the error), but afterwards he should still be able to use the workflow, perhaps with widgets that failed to migrate set to default settings.

##### Actual behavior
Currently the workflow is blocked. Nothing works, even widgets on parallel branches, until the problematic widget is deleted.

I can see blocking nodes in the log:

    Blocking nodes: {SchemeNode(description_id=Orange.widgets.visualize.owscatterplot, title='Scatter Plot', ...)}

##### Steps to reproduce the behavior
Raise an Exception in `migrate_settings` or `migrate_context`.

##### Additional info (worksheets, data, screenshots, ...)
This is how the workflow with a crash looks like. 

![ss](https://user-images.githubusercontent.com/552182/48558415-84553200-e8e9-11e8-836e-55bb7794e7de.png)




Discussion:

Resolving patch:
diff --git a/Orange/widgets/settings.py b/Orange/widgets/settings.py
--- a/Orange/widgets/settings.py
+++ b/Orange/widgets/settings.py
@@ -29,6 +29,7 @@
 (initializing) the value of the setting attributes.
 """
 
+import sys
 import copy
 import itertools
 import os
@@ -240,9 +241,9 @@ def unpack(self, instance, data):
         data : dict
             packed data
         """
-        for setting, data, instance in self.traverse_settings(data, instance):
-            if setting.name in data and instance is not None:
-                setattr(instance, setting.name, data[setting.name])
+        for setting, _data, inst in self.traverse_settings(data, instance):
+            if setting.name in _data and inst is not None:
+                setattr(inst, setting.name, _data[setting.name])
 
     def get_provider(self, provider_class):
         """Return provider for provider_class.
@@ -261,6 +262,7 @@ def get_provider(self, provider_class):
             provider = subprovider.get_provider(provider_class)
             if provider:
                 return provider
+        return None
 
     def traverse_settings(self, data=None, instance=None):
         """Generator of tuples (setting, data, instance) for each setting
@@ -462,7 +464,12 @@ def initialize(self, instance, data=None):
     def _migrate_settings(self, settings):
         """Ask widget to migrate settings to the latest version."""
         if settings:
-            self.widget_class.migrate_settings(settings, settings.pop(VERSION_KEY, 0))
+            try:
+                self.widget_class.migrate_settings(
+                    settings, settings.pop(VERSION_KEY, 0))
+            except Exception:  # pylint: disable=broad-except
+                sys.excepthook(*sys.exc_info())
+                settings.clear()
 
     def _select_provider(self, instance):
         provider = self.provider.get_provider(instance.__class__)
@@ -538,9 +545,9 @@ def reset_settings(self, instance):
         ----------
         instance : OWWidget
         """
-        for setting, _, instance in self.provider.traverse_settings(instance=instance):
+        for setting, _, inst in self.provider.traverse_settings(instance=instance):
             if setting.packable:
-                setattr(instance, setting.name, setting.default)
+                setattr(inst, setting.name, setting.default)
 
 
 class ContextSetting(Setting):
@@ -633,6 +640,9 @@ def _migrate_contexts(self, contexts):
                     context, context.values.pop(VERSION_KEY, 0))
             except IncompatibleContext:
                 del contexts[i]
+            except Exception:  # pylint: disable=broad-except
+                sys.excepthook(*sys.exc_info())
+                del contexts[i]
             else:
                 i += 1
 


Test patch: