Repository: open-telemetry/opentelemetry-python

Issue:
MetricReaders must be registered in only one MeterProvider instance
From the [spec](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#metricreader):

> The SDK MUST NOT allow a MetricReader instance to be registered on more than one MeterProvider instance.


Discussion:

Resolving patch:
diff --git a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py
--- a/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py
+++ b/opentelemetry-sdk/src/opentelemetry/sdk/_metrics/__init__.py
@@ -177,6 +177,9 @@ class MeterProvider(APIMeterProvider):
         )
     """
 
+    _all_metric_readers_lock = Lock()
+    _all_metric_readers = set()
+
     def __init__(
         self,
         metric_readers: Sequence[MetricReader] = (),
@@ -200,9 +203,18 @@ def __init__(
             self._atexit_handler = register(self.shutdown)
 
         self._meters = {}
-        self._metric_readers = metric_readers
 
         for metric_reader in self._sdk_config.metric_readers:
+
+            with self._all_metric_readers_lock:
+                if metric_reader in self._all_metric_readers:
+                    raise Exception(
+                        f"MetricReader {metric_reader} has been registered "
+                        "already in other MeterProvider instance"
+                    )
+
+                self._all_metric_readers.add(metric_reader)
+
             metric_reader._set_collect_callback(
                 self._measurement_consumer.collect
             )


Test patch:
diff --git a/opentelemetry-sdk/tests/metrics/test_metrics.py b/opentelemetry-sdk/tests/metrics/test_metrics.py
--- a/opentelemetry-sdk/tests/metrics/test_metrics.py
+++ b/opentelemetry-sdk/tests/metrics/test_metrics.py
@@ -19,6 +19,7 @@
 
 from opentelemetry._metrics import NoOpMeter
 from opentelemetry.sdk._metrics import Meter, MeterProvider
+from opentelemetry.sdk._metrics.export import PeriodicExportingMetricReader
 from opentelemetry.sdk._metrics.instrument import (
     Counter,
     Histogram,
@@ -45,6 +46,25 @@ def shutdown(self):
 
 
 class TestMeterProvider(ConcurrencyTestBase):
+    def tearDown(self):
+
+        MeterProvider._all_metric_readers = set()
+
+    def test_register_metric_readers(self):
+
+        metric_reader_0 = PeriodicExportingMetricReader(Mock())
+        metric_reader_1 = PeriodicExportingMetricReader(Mock())
+
+        try:
+            MeterProvider(metric_readers=(metric_reader_0,))
+            MeterProvider(metric_readers=(metric_reader_1,))
+        except Exception as error:
+            self.fail(f"Unexpected exception {error} raised")
+
+        with self.assertRaises(Exception):
+            MeterProvider(metric_readers=(metric_reader_0,))
+            MeterProvider(metric_readers=(metric_reader_0,))
+
     def test_resource(self):
         """
         `MeterProvider` provides a way to allow a `Resource` to be specified.
@@ -190,7 +210,13 @@ def test_shutdown_race(self, mock_logger):
     def test_measurement_collect_callback(
         self, mock_sync_measurement_consumer
     ):
-        metric_readers = [DummyMetricReader()] * 5
+        metric_readers = [
+            DummyMetricReader(),
+            DummyMetricReader(),
+            DummyMetricReader(),
+            DummyMetricReader(),
+            DummyMetricReader(),
+        ]
         sync_consumer_instance = mock_sync_measurement_consumer()
         sync_consumer_instance.collect = MockFunc()
         MeterProvider(metric_readers=metric_readers)