We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Script Reloader does not unload old script
##### Steps to reproduce the problem:

1. mitmdump -s script.py
2. Modify script on disk.

##### System information

Mitmproxy version: 3.0.0 (2.0.0dev0407-0x315daa0)
Python version: 3.5.2
Platform: Linux-4.4.0-43-Microsoft-x86_64-with-Ubuntu-16.04-xenial
SSL version: OpenSSL 1.0.2g  1 Mar 2016
Linux distro: Ubuntu 16.04 xenial

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `mitmproxy/addons/script.py`
Content:
```
1 import os
2 import importlib
3 import time
4 import sys
5 import typing
6 
7 from mitmproxy import addonmanager
8 from mitmproxy import exceptions
9 from mitmproxy import flow
10 from mitmproxy import command
11 from mitmproxy import eventsequence
12 from mitmproxy import ctx
13 
14 
15 def load_script(actx, path):
16     if not os.path.exists(path):
17         ctx.log.info("No such file: %s" % path)
18         return
19     loader = importlib.machinery.SourceFileLoader(os.path.basename(path), path)
20     try:
21         oldpath = sys.path
22         sys.path.insert(0, os.path.dirname(path))
23         with addonmanager.safecall():
24             m = loader.load_module()
25             if not getattr(m, "name", None):
26                 m.name = path
27             return m
28     finally:
29         sys.path[:] = oldpath
30 
31 
32 class Script:
33     """
34         An addon that manages a single script.
35     """
36     ReloadInterval = 2
37 
38     def __init__(self, path):
39         self.name = "scriptmanager:" + path
40         self.path = path
41         self.fullpath = os.path.expanduser(path)
42         self.ns = None
43 
44         self.last_load = 0
45         self.last_mtime = 0
46         if not os.path.isfile(self.fullpath):
47             raise exceptions.OptionsError("No such script: %s" % path)
48 
49     @property
50     def addons(self):
51         return [self.ns] if self.ns else []
52 
53     def tick(self):
54         if time.time() - self.last_load > self.ReloadInterval:
55             mtime = os.stat(self.fullpath).st_mtime
56             if mtime > self.last_mtime:
57                 ctx.log.info("Loading script: %s" % self.path)
58                 if self.ns:
59                     ctx.master.addons.remove(self.ns)
60                 self.ns = load_script(ctx, self.fullpath)
61                 if self.ns:
62                     # We're already running, so we have to explicitly register and
63                     # configure the addon
64                     ctx.master.addons.register(self.ns)
65                     ctx.master.addons.invoke_addon(self.ns, "running")
66                     ctx.master.addons.invoke_addon(
67                         self.ns,
68                         "configure",
69                         ctx.options.keys()
70                     )
71                 self.last_load = time.time()
72                 self.last_mtime = mtime
73 
74 
75 class ScriptLoader:
76     """
77         An addon that manages loading scripts from options.
78     """
79     def __init__(self):
80         self.is_running = False
81         self.addons = []
82 
83     def running(self):
84         self.is_running = True
85 
86     @command.command("script.run")
87     def script_run(self, flows: typing.Sequence[flow.Flow], path: str) -> None:
88         """
89             Run a script on the specified flows. The script is loaded with
90             default options, and all lifecycle events for each flow are
91             simulated.
92         """
93         try:
94             s = Script(path)
95             l = addonmanager.Loader(ctx.master)
96             ctx.master.addons.invoke_addon(s, "load", l)
97             ctx.master.addons.invoke_addon(s, "configure", ctx.options.keys())
98             # Script is loaded on the first tick
99             ctx.master.addons.invoke_addon(s, "tick")
100             for f in flows:
101                 for evt, arg in eventsequence.iterate(f):
102                     ctx.master.addons.invoke_addon(s, evt, arg)
103         except exceptions.OptionsError as e:
104             raise exceptions.CommandError("Error running script: %s" % e) from e
105 
106     def configure(self, updated):
107         if "scripts" in updated:
108             for s in ctx.options.scripts:
109                 if ctx.options.scripts.count(s) > 1:
110                     raise exceptions.OptionsError("Duplicate script: %s" % s)
111 
112             for a in self.addons[:]:
113                 if a.path not in ctx.options.scripts:
114                     ctx.log.info("Un-loading script: %s" % a.name)
115                     ctx.master.addons.remove(a)
116                     self.addons.remove(a)
117 
118             # The machinations below are to ensure that:
119             #   - Scripts remain in the same order
120             #   - Scripts are not initialized un-necessarily. If only a
121             #   script's order in the script list has changed, it is just
122             #   moved.
123 
124             current = {}
125             for a in self.addons:
126                 current[a.path] = a
127 
128             ordered = []
129             newscripts = []
130             for s in ctx.options.scripts:
131                 if s in current:
132                     ordered.append(current[s])
133                 else:
134                     sc = Script(s)
135                     ordered.append(sc)
136                     newscripts.append(sc)
137 
138             self.addons = ordered
139 
140             for s in newscripts:
141                 ctx.master.addons.register(s)
142                 if self.is_running:
143                     # If we're already running, we configure and tell the addon
144                     # we're up and running.
145                     ctx.master.addons.invoke_addon(s, "running")
146
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py
--- a/mitmproxy/addons/script.py
+++ b/mitmproxy/addons/script.py
@@ -52,11 +52,19 @@
 
     def tick(self):
         if time.time() - self.last_load > self.ReloadInterval:
-            mtime = os.stat(self.fullpath).st_mtime
+            try:
+                mtime = os.stat(self.fullpath).st_mtime
+            except FileNotFoundError:
+                scripts = ctx.options.scripts
+                scripts.remove(self.path)
+                ctx.options.update(scripts=scripts)
+                return
+
             if mtime > self.last_mtime:
                 ctx.log.info("Loading script: %s" % self.path)
                 if self.ns:
                     ctx.master.addons.remove(self.ns)
+                    del sys.modules[self.ns.__name__]
                 self.ns = load_script(ctx, self.fullpath)
                 if self.ns:
                     # We're already running, so we have to explicitly register and

{"golden_diff": "diff --git a/mitmproxy/addons/script.py b/mitmproxy/addons/script.py\n--- a/mitmproxy/addons/script.py\n+++ b/mitmproxy/addons/script.py\n@@ -52,11 +52,19 @@\n \n     def tick(self):\n         if time.time() - self.last_load > self.ReloadInterval:\n-            mtime = os.stat(self.fullpath).st_mtime\n+            try:\n+                mtime = os.stat(self.fullpath).st_mtime\n+            except FileNotFoundError:\n+                scripts = ctx.options.scripts\n+                scripts.remove(self.path)\n+                ctx.options.update(scripts=scripts)\n+                return\n+\n             if mtime > self.last_mtime:\n                 ctx.log.info(\"Loading script: %s\" % self.path)\n                 if self.ns:\n                     ctx.master.addons.remove(self.ns)\n+                    del sys.modules[self.ns.__name__]\n                 self.ns = load_script(ctx, self.fullpath)\n                 if self.ns:\n                     # We're already running, so we have to explicitly register and\n", "issue": "Script Reloader does not unload old script\n##### Steps to reproduce the problem:\r\n\r\n1. mitmdump -s script.py\r\n2. Modify script on disk.\r\n\r\n##### System information\r\n\r\nMitmproxy version: 3.0.0 (2.0.0dev0407-0x315daa0)\r\nPython version: 3.5.2\r\nPlatform: Linux-4.4.0-43-Microsoft-x86_64-with-Ubuntu-16.04-xenial\r\nSSL version: OpenSSL 1.0.2g  1 Mar 2016\r\nLinux distro: Ubuntu 16.04 xenial\n", "before_files": [{"content": "import os\nimport importlib\nimport time\nimport sys\nimport typing\n\nfrom mitmproxy import addonmanager\nfrom mitmproxy import exceptions\nfrom mitmproxy import flow\nfrom mitmproxy import command\nfrom mitmproxy import eventsequence\nfrom mitmproxy import ctx\n\n\ndef load_script(actx, path):\n    if not os.path.exists(path):\n        ctx.log.info(\"No such file: %s\" % path)\n        return\n    loader = importlib.machinery.SourceFileLoader(os.path.basename(path), path)\n    try:\n        oldpath = sys.path\n        sys.path.insert(0, os.path.dirname(path))\n        with addonmanager.safecall():\n            m = loader.load_module()\n            if not getattr(m, \"name\", None):\n                m.name = path\n            return m\n    finally:\n        sys.path[:] = oldpath\n\n\nclass Script:\n    \"\"\"\n        An addon that manages a single script.\n    \"\"\"\n    ReloadInterval = 2\n\n    def __init__(self, path):\n        self.name = \"scriptmanager:\" + path\n        self.path = path\n        self.fullpath = os.path.expanduser(path)\n        self.ns = None\n\n        self.last_load = 0\n        self.last_mtime = 0\n        if not os.path.isfile(self.fullpath):\n            raise exceptions.OptionsError(\"No such script: %s\" % path)\n\n    @property\n    def addons(self):\n        return [self.ns] if self.ns else []\n\n    def tick(self):\n        if time.time() - self.last_load > self.ReloadInterval:\n            mtime = os.stat(self.fullpath).st_mtime\n            if mtime > self.last_mtime:\n                ctx.log.info(\"Loading script: %s\" % self.path)\n                if self.ns:\n                    ctx.master.addons.remove(self.ns)\n                self.ns = load_script(ctx, self.fullpath)\n                if self.ns:\n                    # We're already running, so we have to explicitly register and\n                    # configure the addon\n                    ctx.master.addons.register(self.ns)\n                    ctx.master.addons.invoke_addon(self.ns, \"running\")\n                    ctx.master.addons.invoke_addon(\n                        self.ns,\n                        \"configure\",\n                        ctx.options.keys()\n                    )\n                self.last_load = time.time()\n                self.last_mtime = mtime\n\n\nclass ScriptLoader:\n    \"\"\"\n        An addon that manages loading scripts from options.\n    \"\"\"\n    def __init__(self):\n        self.is_running = False\n        self.addons = []\n\n    def running(self):\n        self.is_running = True\n\n    @command.command(\"script.run\")\n    def script_run(self, flows: typing.Sequence[flow.Flow], path: str) -> None:\n        \"\"\"\n            Run a script on the specified flows. The script is loaded with\n            default options, and all lifecycle events for each flow are\n            simulated.\n        \"\"\"\n        try:\n            s = Script(path)\n            l = addonmanager.Loader(ctx.master)\n            ctx.master.addons.invoke_addon(s, \"load\", l)\n            ctx.master.addons.invoke_addon(s, \"configure\", ctx.options.keys())\n            # Script is loaded on the first tick\n            ctx.master.addons.invoke_addon(s, \"tick\")\n            for f in flows:\n                for evt, arg in eventsequence.iterate(f):\n                    ctx.master.addons.invoke_addon(s, evt, arg)\n        except exceptions.OptionsError as e:\n            raise exceptions.CommandError(\"Error running script: %s\" % e) from e\n\n    def configure(self, updated):\n        if \"scripts\" in updated:\n            for s in ctx.options.scripts:\n                if ctx.options.scripts.count(s) > 1:\n                    raise exceptions.OptionsError(\"Duplicate script: %s\" % s)\n\n            for a in self.addons[:]:\n                if a.path not in ctx.options.scripts:\n                    ctx.log.info(\"Un-loading script: %s\" % a.name)\n                    ctx.master.addons.remove(a)\n                    self.addons.remove(a)\n\n            # The machinations below are to ensure that:\n            #   - Scripts remain in the same order\n            #   - Scripts are not initialized un-necessarily. If only a\n            #   script's order in the script list has changed, it is just\n            #   moved.\n\n            current = {}\n            for a in self.addons:\n                current[a.path] = a\n\n            ordered = []\n            newscripts = []\n            for s in ctx.options.scripts:\n                if s in current:\n                    ordered.append(current[s])\n                else:\n                    sc = Script(s)\n                    ordered.append(sc)\n                    newscripts.append(sc)\n\n            self.addons = ordered\n\n            for s in newscripts:\n                ctx.master.addons.register(s)\n                if self.is_running:\n                    # If we're already running, we configure and tell the addon\n                    # we're up and running.\n                    ctx.master.addons.invoke_addon(s, \"running\")\n", "path": "mitmproxy/addons/script.py"}], "after_files": [{"content": "import os\nimport importlib\nimport time\nimport sys\nimport typing\n\nfrom mitmproxy import addonmanager\nfrom mitmproxy import exceptions\nfrom mitmproxy import flow\nfrom mitmproxy import command\nfrom mitmproxy import eventsequence\nfrom mitmproxy import ctx\n\n\ndef load_script(actx, path):\n    if not os.path.exists(path):\n        ctx.log.info(\"No such file: %s\" % path)\n        return\n    loader = importlib.machinery.SourceFileLoader(os.path.basename(path), path)\n    try:\n        oldpath = sys.path\n        sys.path.insert(0, os.path.dirname(path))\n        with addonmanager.safecall():\n            m = loader.load_module()\n            if not getattr(m, \"name\", None):\n                m.name = path\n            return m\n    finally:\n        sys.path[:] = oldpath\n\n\nclass Script:\n    \"\"\"\n        An addon that manages a single script.\n    \"\"\"\n    ReloadInterval = 2\n\n    def __init__(self, path):\n        self.name = \"scriptmanager:\" + path\n        self.path = path\n        self.fullpath = os.path.expanduser(path)\n        self.ns = None\n\n        self.last_load = 0\n        self.last_mtime = 0\n        if not os.path.isfile(self.fullpath):\n            raise exceptions.OptionsError(\"No such script: %s\" % path)\n\n    @property\n    def addons(self):\n        return [self.ns] if self.ns else []\n\n    def tick(self):\n        if time.time() - self.last_load > self.ReloadInterval:\n            try:\n                mtime = os.stat(self.fullpath).st_mtime\n            except FileNotFoundError:\n                scripts = ctx.options.scripts\n                scripts.remove(self.path)\n                ctx.options.update(scripts=scripts)\n                return\n\n            if mtime > self.last_mtime:\n                ctx.log.info(\"Loading script: %s\" % self.path)\n                if self.ns:\n                    ctx.master.addons.remove(self.ns)\n                    del sys.modules[self.ns.__name__]\n                self.ns = load_script(ctx, self.fullpath)\n                if self.ns:\n                    # We're already running, so we have to explicitly register and\n                    # configure the addon\n                    ctx.master.addons.register(self.ns)\n                    ctx.master.addons.invoke_addon(self.ns, \"running\")\n                    ctx.master.addons.invoke_addon(\n                        self.ns,\n                        \"configure\",\n                        ctx.options.keys()\n                    )\n                self.last_load = time.time()\n                self.last_mtime = mtime\n\n\nclass ScriptLoader:\n    \"\"\"\n        An addon that manages loading scripts from options.\n    \"\"\"\n    def __init__(self):\n        self.is_running = False\n        self.addons = []\n\n    def running(self):\n        self.is_running = True\n\n    @command.command(\"script.run\")\n    def script_run(self, flows: typing.Sequence[flow.Flow], path: str) -> None:\n        \"\"\"\n            Run a script on the specified flows. The script is loaded with\n            default options, and all lifecycle events for each flow are\n            simulated.\n        \"\"\"\n        try:\n            s = Script(path)\n            l = addonmanager.Loader(ctx.master)\n            ctx.master.addons.invoke_addon(s, \"load\", l)\n            ctx.master.addons.invoke_addon(s, \"configure\", ctx.options.keys())\n            # Script is loaded on the first tick\n            ctx.master.addons.invoke_addon(s, \"tick\")\n            for f in flows:\n                for evt, arg in eventsequence.iterate(f):\n                    ctx.master.addons.invoke_addon(s, evt, arg)\n        except exceptions.OptionsError as e:\n            raise exceptions.CommandError(\"Error running script: %s\" % e) from e\n\n    def configure(self, updated):\n        if \"scripts\" in updated:\n            for s in ctx.options.scripts:\n                if ctx.options.scripts.count(s) > 1:\n                    raise exceptions.OptionsError(\"Duplicate script: %s\" % s)\n\n            for a in self.addons[:]:\n                if a.path not in ctx.options.scripts:\n                    ctx.log.info(\"Un-loading script: %s\" % a.name)\n                    ctx.master.addons.remove(a)\n                    self.addons.remove(a)\n\n            # The machinations below are to ensure that:\n            #   - Scripts remain in the same order\n            #   - Scripts are not initialized un-necessarily. If only a\n            #   script's order in the script list has changed, it is just\n            #   moved.\n\n            current = {}\n            for a in self.addons:\n                current[a.path] = a\n\n            ordered = []\n            newscripts = []\n            for s in ctx.options.scripts:\n                if s in current:\n                    ordered.append(current[s])\n                else:\n                    sc = Script(s)\n                    ordered.append(sc)\n                    newscripts.append(sc)\n\n            self.addons = ordered\n\n            for s in newscripts:\n                ctx.master.addons.register(s)\n                if self.is_running:\n                    # If we're already running, we configure and tell the addon\n                    # we're up and running.\n                    ctx.master.addons.invoke_addon(s, \"running\")\n", "path": "mitmproxy/addons/script.py"}]}