We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Plugins run twice in autoreload mode
<!--
  Hi there! Thank you for discovering and submitting an issue.

  Before you submit this, let’s make sure of a few things.
  Please make sure the following boxes are ticked if they are correct.
  If not, please try and fulfill them first.
-->

<!-- Checked checkbox should look like this: [x] -->
- [x] I have read the [Filing Issues](https://docs.getpelican.com/en/latest/contribute.html#filing-issues) and subsequent “How to Get Help” sections of the documentation.
- [x] I have searched the [issues](https://github.com/getpelican/pelican/issues?q=is%3Aissue) (including closed ones) and believe that this is not a duplicate.

<!--
  Once the above boxes are checked, if you are able to fill in the following list
  with your information, it would be very helpful for maintainers.
-->

- **OS version and name**: Ubuntu 18.04.5 LTS (WSL)
- **Python version**: 3.8.6
- **Pelican version**: e4d9c41a

## To reproduce

Create these files:

```
.
├── content
│   └── 1.rst
├── pelicanconf.py
└── plugins
    └── test_plugin.py
```

```rst
.. content/1.rst
####
test
####

:date: 1970-01-01
:modified: 1970-01-01

TEST
```

```python
# pelicanconf.py
PLUGINS = [
    'test_plugin',
]
PLUGIN_PATHS = [
    'plugins',
]
```

```python
# plugins/test_plugin.py
import logging

from pelican import signals

logger = logging.getLogger(__name__)


def test_function(content):
    logger.info('test plugin loaded')
    test = content._content
    test += 'TEST'
    content._content = test
    logger.info(content._content)


def register():
    signals.content_object_init.connect(test_function)
```

Then run:

```console
$ pelican -rD --logs-dedup-min-level DEBUG content
[Not showing unrelated logs]
-> test plugin loaded
-> <p>TEST</p>
  | TEST
-> test plugin loaded
-> <p>TEST</p>
  | TESTTEST

$ pelican -D --logs-dedup-min-level DEBUG content
[Not showing unrelated logs]
-> test plugin loaded
-> <p>TEST</p>
  | TEST
```

The plugin runs twice with `-r`, but only once without `-r`.

## The casue of the issue

After a bisect, I found that commit ed1eca16 introduced this issue.

```console
$ git checkout ed1eca16^
$ pelican -rD --logs-dedup-min-level DEBUG content
[Not showing unrelated logs]
-> test plugin loaded
-> <p>TEST</p>
  | TEST

$ git checkout ed1eca16
$ pelican -rD --logs-dedup-min-level DEBUG content
[Not showing unrelated logs]
-> test plugin loaded
-> <p>TEST</p>
  | TEST
-> test plugin loaded
-> <p>TEST</p>
  | TESTTEST
```

After adding this line, I found that the plugin was registered twice:

```diff
diff --git a/pelican/contents.py b/pelican/contents.py
index 594cd3b5..ff991be9 100644
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -139,6 +139,7 @@ class Content(object):
         if 'summary' in metadata:
             self._summary = metadata['summary']

+        logger.info(str(signals.content_object_init.receivers))
         signals.content_object_init.send(self)

     def __str__(self):
```

```console
$ pelican -rD --logs-dedup-min-level DEBUG content
[Not showing unrelated logs]
-> {140542259499216: <weakref at 0x7fd28a2b8b80; to 'function' at 0x7fd28b7410d0 (test_function)>, 140542259600400: <weakref at 0x7fd28b7525e0; to 'function' at 0x7fd28b759c10 (test_function)>}
-> test plugin loaded
-> <p>TEST</p>
  | TEST
-> test plugin loaded
-> <p>TEST</p>
  | TESTTEST
```

But why? The reason:

```python
>>> # Implementation of ed1eca16^
>>> import sys
>>> sys.path.insert(0, 'plugins')
>>> plugin = __import__('test_plugin', globals(), locals(), str('module'))
>>> plugin_ = __import__('test_plugin', globals(), locals(), str('module'))
>>> plugin is plugin_
True
>>>
>>> # Implementation of ed1eca16
>>> import importlib
>>> spec = importlib.machinery.PathFinder.find_spec('test_plugin', ['plugins'])
>>> plugin = importlib.util.module_from_spec(spec)
>>> plugin_ = importlib.util.module_from_spec(spec)
>>> plugin is plugin_
False
```

## How to fix

To fix it, simply avoid loading the same plugin twice:

```diff
diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py
index 4e6ec3c5..699192d3 100644
--- a/pelican/plugins/_utils.py
+++ b/pelican/plugins/_utils.py
@@ -53,6 +53,11 @@ def load_legacy_plugin(plugin, plugin_paths):
     if spec is None:
         raise ImportError('Cannot import plugin `{}`'.format(plugin))
     else:
+        # Avoid loading the same plugin twice
+        try:
+            return sys.modules[spec.name]
+        except KeyError:
+            pass
         # create module object from spec
         mod = importlib.util.module_from_spec(spec)
         # place it into sys.modules cache
```

I will create a pull request after writing some tests for it.



--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `pelican/plugins/_utils.py`
Content:
```
1 import importlib
2 import importlib.machinery
3 import importlib.util
4 import logging
5 import pkgutil
6 import sys
7 
8 
9 logger = logging.getLogger(__name__)
10 
11 
12 def iter_namespace(ns_pkg):
13     # Specifying the second argument (prefix) to iter_modules makes the
14     # returned name an absolute name instead of a relative one. This allows
15     # import_module to work without having to do additional modification to
16     # the name.
17     return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + ".")
18 
19 
20 def get_namespace_plugins(ns_pkg=None):
21     if ns_pkg is None:
22         import pelican.plugins as ns_pkg
23 
24     return {
25         name: importlib.import_module(name)
26         for finder, name, ispkg
27         in iter_namespace(ns_pkg)
28         if ispkg
29     }
30 
31 
32 def list_plugins(ns_pkg=None):
33     from pelican.log import init as init_logging
34     init_logging(logging.INFO)
35     ns_plugins = get_namespace_plugins(ns_pkg)
36     if ns_plugins:
37         logger.info('Plugins found:\n' + '\n'.join(ns_plugins))
38     else:
39         logger.info('No plugins are installed')
40 
41 
42 def load_legacy_plugin(plugin, plugin_paths):
43     if '.' in plugin:
44         # it is in a package, try to resolve package first
45         package, _, _ = plugin.rpartition('.')
46         load_legacy_plugin(package, plugin_paths)
47 
48     # Try to find plugin in PLUGIN_PATHS
49     spec = importlib.machinery.PathFinder.find_spec(plugin, plugin_paths)
50     if spec is None:
51         # If failed, try to find it in normal importable locations
52         spec = importlib.util.find_spec(plugin)
53     if spec is None:
54         raise ImportError('Cannot import plugin `{}`'.format(plugin))
55     else:
56         # create module object from spec
57         mod = importlib.util.module_from_spec(spec)
58         # place it into sys.modules cache
59         # necessary if module imports itself at some point (e.g. packages)
60         sys.modules[spec.name] = mod
61         try:
62             # try to execute it inside module object
63             spec.loader.exec_module(mod)
64         except Exception:  # problem with import
65             try:
66                 # remove module from sys.modules since it can't be loaded
67                 del sys.modules[spec.name]
68             except KeyError:
69                 pass
70             raise
71 
72         # if all went well, we have the plugin module
73         return mod
74 
75 
76 def load_plugins(settings):
77     logger.debug('Finding namespace plugins')
78     namespace_plugins = get_namespace_plugins()
79     if namespace_plugins:
80         logger.debug('Namespace plugins found:\n' +
81                      '\n'.join(namespace_plugins))
82     plugins = []
83     if settings.get('PLUGINS') is not None:
84         for plugin in settings['PLUGINS']:
85             if isinstance(plugin, str):
86                 logger.debug('Loading plugin `%s`', plugin)
87                 # try to find in namespace plugins
88                 if plugin in namespace_plugins:
89                     plugin = namespace_plugins[plugin]
90                 elif 'pelican.plugins.{}'.format(plugin) in namespace_plugins:
91                     plugin = namespace_plugins['pelican.plugins.{}'.format(
92                         plugin)]
93                 # try to import it
94                 else:
95                     try:
96                         plugin = load_legacy_plugin(
97                             plugin,
98                             settings.get('PLUGIN_PATHS', []))
99                     except ImportError as e:
100                         logger.error('Cannot load plugin `%s`\n%s', plugin, e)
101                         continue
102             plugins.append(plugin)
103     else:
104         plugins = list(namespace_plugins.values())
105 
106     return plugins
107
```
--- 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/pelican/plugins/_utils.py b/pelican/plugins/_utils.py
--- a/pelican/plugins/_utils.py
+++ b/pelican/plugins/_utils.py
@@ -53,6 +53,9 @@
     if spec is None:
         raise ImportError('Cannot import plugin `{}`'.format(plugin))
     else:
+        # Avoid loading the same plugin twice
+        if spec.name in sys.modules:
+            return sys.modules[spec.name]
         # create module object from spec
         mod = importlib.util.module_from_spec(spec)
         # place it into sys.modules cache

{"golden_diff": "diff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py\n--- a/pelican/plugins/_utils.py\n+++ b/pelican/plugins/_utils.py\n@@ -53,6 +53,9 @@\n     if spec is None:\n         raise ImportError('Cannot import plugin `{}`'.format(plugin))\n     else:\n+        # Avoid loading the same plugin twice\n+        if spec.name in sys.modules:\n+            return sys.modules[spec.name]\n         # create module object from spec\n         mod = importlib.util.module_from_spec(spec)\n         # place it into sys.modules cache\n", "issue": "Plugins run twice in autoreload mode\n<!--\r\n  Hi there! Thank you for discovering and submitting an issue.\r\n\r\n  Before you submit this, let\u2019s make sure of a few things.\r\n  Please make sure the following boxes are ticked if they are correct.\r\n  If not, please try and fulfill them first.\r\n-->\r\n\r\n<!-- Checked checkbox should look like this: [x] -->\r\n- [x] I have read the [Filing Issues](https://docs.getpelican.com/en/latest/contribute.html#filing-issues) and subsequent \u201cHow to Get Help\u201d sections of the documentation.\r\n- [x] I have searched the [issues](https://github.com/getpelican/pelican/issues?q=is%3Aissue) (including closed ones) and believe that this is not a duplicate.\r\n\r\n<!--\r\n  Once the above boxes are checked, if you are able to fill in the following list\r\n  with your information, it would be very helpful for maintainers.\r\n-->\r\n\r\n- **OS version and name**: Ubuntu 18.04.5 LTS (WSL)\r\n- **Python version**: 3.8.6\r\n- **Pelican version**: e4d9c41a\r\n\r\n## To reproduce\r\n\r\nCreate these files:\r\n\r\n```\r\n.\r\n\u251c\u2500\u2500 content\r\n\u2502   \u2514\u2500\u2500 1.rst\r\n\u251c\u2500\u2500 pelicanconf.py\r\n\u2514\u2500\u2500 plugins\r\n    \u2514\u2500\u2500 test_plugin.py\r\n```\r\n\r\n```rst\r\n.. content/1.rst\r\n####\r\ntest\r\n####\r\n\r\n:date: 1970-01-01\r\n:modified: 1970-01-01\r\n\r\nTEST\r\n```\r\n\r\n```python\r\n# pelicanconf.py\r\nPLUGINS = [\r\n    'test_plugin',\r\n]\r\nPLUGIN_PATHS = [\r\n    'plugins',\r\n]\r\n```\r\n\r\n```python\r\n# plugins/test_plugin.py\r\nimport logging\r\n\r\nfrom pelican import signals\r\n\r\nlogger = logging.getLogger(__name__)\r\n\r\n\r\ndef test_function(content):\r\n    logger.info('test plugin loaded')\r\n    test = content._content\r\n    test += 'TEST'\r\n    content._content = test\r\n    logger.info(content._content)\r\n\r\n\r\ndef register():\r\n    signals.content_object_init.connect(test_function)\r\n```\r\n\r\nThen run:\r\n\r\n```console\r\n$ pelican -rD --logs-dedup-min-level DEBUG content\r\n[Not showing unrelated logs]\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TEST\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TESTTEST\r\n\r\n$ pelican -D --logs-dedup-min-level DEBUG content\r\n[Not showing unrelated logs]\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TEST\r\n```\r\n\r\nThe plugin runs twice with `-r`, but only once without `-r`.\r\n\r\n## The casue of the issue\r\n\r\nAfter a bisect, I found that commit ed1eca16 introduced this issue.\r\n\r\n```console\r\n$ git checkout ed1eca16^\r\n$ pelican -rD --logs-dedup-min-level DEBUG content\r\n[Not showing unrelated logs]\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TEST\r\n\r\n$ git checkout ed1eca16\r\n$ pelican -rD --logs-dedup-min-level DEBUG content\r\n[Not showing unrelated logs]\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TEST\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TESTTEST\r\n```\r\n\r\nAfter adding this line, I found that the plugin was registered twice:\r\n\r\n```diff\r\ndiff --git a/pelican/contents.py b/pelican/contents.py\r\nindex 594cd3b5..ff991be9 100644\r\n--- a/pelican/contents.py\r\n+++ b/pelican/contents.py\r\n@@ -139,6 +139,7 @@ class Content(object):\r\n         if 'summary' in metadata:\r\n             self._summary = metadata['summary']\r\n\r\n+        logger.info(str(signals.content_object_init.receivers))\r\n         signals.content_object_init.send(self)\r\n\r\n     def __str__(self):\r\n```\r\n\r\n```console\r\n$ pelican -rD --logs-dedup-min-level DEBUG content\r\n[Not showing unrelated logs]\r\n-> {140542259499216: <weakref at 0x7fd28a2b8b80; to 'function' at 0x7fd28b7410d0 (test_function)>, 140542259600400: <weakref at 0x7fd28b7525e0; to 'function' at 0x7fd28b759c10 (test_function)>}\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TEST\r\n-> test plugin loaded\r\n-> <p>TEST</p>\r\n  | TESTTEST\r\n```\r\n\r\nBut why? The reason:\r\n\r\n```python\r\n>>> # Implementation of ed1eca16^\r\n>>> import sys\r\n>>> sys.path.insert(0, 'plugins')\r\n>>> plugin = __import__('test_plugin', globals(), locals(), str('module'))\r\n>>> plugin_ = __import__('test_plugin', globals(), locals(), str('module'))\r\n>>> plugin is plugin_\r\nTrue\r\n>>>\r\n>>> # Implementation of ed1eca16\r\n>>> import importlib\r\n>>> spec = importlib.machinery.PathFinder.find_spec('test_plugin', ['plugins'])\r\n>>> plugin = importlib.util.module_from_spec(spec)\r\n>>> plugin_ = importlib.util.module_from_spec(spec)\r\n>>> plugin is plugin_\r\nFalse\r\n```\r\n\r\n## How to fix\r\n\r\nTo fix it, simply avoid loading the same plugin twice:\r\n\r\n```diff\r\ndiff --git a/pelican/plugins/_utils.py b/pelican/plugins/_utils.py\r\nindex 4e6ec3c5..699192d3 100644\r\n--- a/pelican/plugins/_utils.py\r\n+++ b/pelican/plugins/_utils.py\r\n@@ -53,6 +53,11 @@ def load_legacy_plugin(plugin, plugin_paths):\r\n     if spec is None:\r\n         raise ImportError('Cannot import plugin `{}`'.format(plugin))\r\n     else:\r\n+        # Avoid loading the same plugin twice\r\n+        try:\r\n+            return sys.modules[spec.name]\r\n+        except KeyError:\r\n+            pass\r\n         # create module object from spec\r\n         mod = importlib.util.module_from_spec(spec)\r\n         # place it into sys.modules cache\r\n```\r\n\r\nI will create a pull request after writing some tests for it.\r\n\r\n\n", "before_files": [{"content": "import importlib\nimport importlib.machinery\nimport importlib.util\nimport logging\nimport pkgutil\nimport sys\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef iter_namespace(ns_pkg):\n    # Specifying the second argument (prefix) to iter_modules makes the\n    # returned name an absolute name instead of a relative one. This allows\n    # import_module to work without having to do additional modification to\n    # the name.\n    return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + \".\")\n\n\ndef get_namespace_plugins(ns_pkg=None):\n    if ns_pkg is None:\n        import pelican.plugins as ns_pkg\n\n    return {\n        name: importlib.import_module(name)\n        for finder, name, ispkg\n        in iter_namespace(ns_pkg)\n        if ispkg\n    }\n\n\ndef list_plugins(ns_pkg=None):\n    from pelican.log import init as init_logging\n    init_logging(logging.INFO)\n    ns_plugins = get_namespace_plugins(ns_pkg)\n    if ns_plugins:\n        logger.info('Plugins found:\\n' + '\\n'.join(ns_plugins))\n    else:\n        logger.info('No plugins are installed')\n\n\ndef load_legacy_plugin(plugin, plugin_paths):\n    if '.' in plugin:\n        # it is in a package, try to resolve package first\n        package, _, _ = plugin.rpartition('.')\n        load_legacy_plugin(package, plugin_paths)\n\n    # Try to find plugin in PLUGIN_PATHS\n    spec = importlib.machinery.PathFinder.find_spec(plugin, plugin_paths)\n    if spec is None:\n        # If failed, try to find it in normal importable locations\n        spec = importlib.util.find_spec(plugin)\n    if spec is None:\n        raise ImportError('Cannot import plugin `{}`'.format(plugin))\n    else:\n        # create module object from spec\n        mod = importlib.util.module_from_spec(spec)\n        # place it into sys.modules cache\n        # necessary if module imports itself at some point (e.g. packages)\n        sys.modules[spec.name] = mod\n        try:\n            # try to execute it inside module object\n            spec.loader.exec_module(mod)\n        except Exception:  # problem with import\n            try:\n                # remove module from sys.modules since it can't be loaded\n                del sys.modules[spec.name]\n            except KeyError:\n                pass\n            raise\n\n        # if all went well, we have the plugin module\n        return mod\n\n\ndef load_plugins(settings):\n    logger.debug('Finding namespace plugins')\n    namespace_plugins = get_namespace_plugins()\n    if namespace_plugins:\n        logger.debug('Namespace plugins found:\\n' +\n                     '\\n'.join(namespace_plugins))\n    plugins = []\n    if settings.get('PLUGINS') is not None:\n        for plugin in settings['PLUGINS']:\n            if isinstance(plugin, str):\n                logger.debug('Loading plugin `%s`', plugin)\n                # try to find in namespace plugins\n                if plugin in namespace_plugins:\n                    plugin = namespace_plugins[plugin]\n                elif 'pelican.plugins.{}'.format(plugin) in namespace_plugins:\n                    plugin = namespace_plugins['pelican.plugins.{}'.format(\n                        plugin)]\n                # try to import it\n                else:\n                    try:\n                        plugin = load_legacy_plugin(\n                            plugin,\n                            settings.get('PLUGIN_PATHS', []))\n                    except ImportError as e:\n                        logger.error('Cannot load plugin `%s`\\n%s', plugin, e)\n                        continue\n            plugins.append(plugin)\n    else:\n        plugins = list(namespace_plugins.values())\n\n    return plugins\n", "path": "pelican/plugins/_utils.py"}], "after_files": [{"content": "import importlib\nimport importlib.machinery\nimport importlib.util\nimport logging\nimport pkgutil\nimport sys\n\n\nlogger = logging.getLogger(__name__)\n\n\ndef iter_namespace(ns_pkg):\n    # Specifying the second argument (prefix) to iter_modules makes the\n    # returned name an absolute name instead of a relative one. This allows\n    # import_module to work without having to do additional modification to\n    # the name.\n    return pkgutil.iter_modules(ns_pkg.__path__, ns_pkg.__name__ + \".\")\n\n\ndef get_namespace_plugins(ns_pkg=None):\n    if ns_pkg is None:\n        import pelican.plugins as ns_pkg\n\n    return {\n        name: importlib.import_module(name)\n        for finder, name, ispkg\n        in iter_namespace(ns_pkg)\n        if ispkg\n    }\n\n\ndef list_plugins(ns_pkg=None):\n    from pelican.log import init as init_logging\n    init_logging(logging.INFO)\n    ns_plugins = get_namespace_plugins(ns_pkg)\n    if ns_plugins:\n        logger.info('Plugins found:\\n' + '\\n'.join(ns_plugins))\n    else:\n        logger.info('No plugins are installed')\n\n\ndef load_legacy_plugin(plugin, plugin_paths):\n    if '.' in plugin:\n        # it is in a package, try to resolve package first\n        package, _, _ = plugin.rpartition('.')\n        load_legacy_plugin(package, plugin_paths)\n\n    # Try to find plugin in PLUGIN_PATHS\n    spec = importlib.machinery.PathFinder.find_spec(plugin, plugin_paths)\n    if spec is None:\n        # If failed, try to find it in normal importable locations\n        spec = importlib.util.find_spec(plugin)\n    if spec is None:\n        raise ImportError('Cannot import plugin `{}`'.format(plugin))\n    else:\n        # Avoid loading the same plugin twice\n        if spec.name in sys.modules:\n            return sys.modules[spec.name]\n        # create module object from spec\n        mod = importlib.util.module_from_spec(spec)\n        # place it into sys.modules cache\n        # necessary if module imports itself at some point (e.g. packages)\n        sys.modules[spec.name] = mod\n        try:\n            # try to execute it inside module object\n            spec.loader.exec_module(mod)\n        except Exception:  # problem with import\n            try:\n                # remove module from sys.modules since it can't be loaded\n                del sys.modules[spec.name]\n            except KeyError:\n                pass\n            raise\n\n        # if all went well, we have the plugin module\n        return mod\n\n\ndef load_plugins(settings):\n    logger.debug('Finding namespace plugins')\n    namespace_plugins = get_namespace_plugins()\n    if namespace_plugins:\n        logger.debug('Namespace plugins found:\\n' +\n                     '\\n'.join(namespace_plugins))\n    plugins = []\n    if settings.get('PLUGINS') is not None:\n        for plugin in settings['PLUGINS']:\n            if isinstance(plugin, str):\n                logger.debug('Loading plugin `%s`', plugin)\n                # try to find in namespace plugins\n                if plugin in namespace_plugins:\n                    plugin = namespace_plugins[plugin]\n                elif 'pelican.plugins.{}'.format(plugin) in namespace_plugins:\n                    plugin = namespace_plugins['pelican.plugins.{}'.format(\n                        plugin)]\n                # try to import it\n                else:\n                    try:\n                        plugin = load_legacy_plugin(\n                            plugin,\n                            settings.get('PLUGIN_PATHS', []))\n                    except ImportError as e:\n                        logger.error('Cannot load plugin `%s`\\n%s', plugin, e)\n                        continue\n            plugins.append(plugin)\n    else:\n        plugins = list(namespace_plugins.values())\n\n    return plugins\n", "path": "pelican/plugins/_utils.py"}]}