Repository: prometheus/client_python
Pull request #1006 (2024-02-11 18:16:52)

wsgi server: address family discovery is not quite right
The code introduced to improve binding to an IPv6 address is based on similar code in Python itself, but is missing some critical arguments to the **socket.getaddrinfo()** call: in particular, the socket type must be set to **SOCK_STREAM**, because we want a TCP connection; the **AI_PASSIVE** flag should also be passed because we intend to use the result for binding a listen socket rather than making an outbound connection.

jclulow: By way of example, this is the output with and without the flags on an Ubuntu 22.04 system:

```python
#!/usr/bin/env python3

import socket

print("WITH type set:")
print(socket.getaddrinfo("0.0.0.0", 9009,
    type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE))

print("WITHOUT type set:")
print(socket.getaddrinfo("0.0.0.0", 9009))
```

```
WITH type set:
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('0.0.0.0', 9009))]
WITHOUT type set:
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('0.0.0.0', 9009)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('0.0.0.0', 9009)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_RAW: 3>, 0, '', ('0.0.0.0', 9009))]
```

On some other platforms, **getaddrinfo()** _requires_ at least the socket type to function as is expected here; e.g.,

```
WITH type set:
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_STREAM: 2>, 6, '', ('0.0.0.0', 9009))]
WITHOUT type set:
Traceback (most recent call last):
  File "/tmp/sigh_test.py", line 10, in <module>
    print(socket.getaddrinfo("0.0.0.0", 9009))
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/usr/lib/python3.11/socket.py", line 962, in getaddrinfo
    for res in _socket.getaddrinfo(host, port, family, type, proto, flags):
               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
socket.gaierror: [Errno 9] service name not available for the specified socket type
```

Patch:
diff --git a/prometheus_client/exposition.py b/prometheus_client/exposition.py
index 30194dd..3a47917 100644
--- a/prometheus_client/exposition.py
+++ b/prometheus_client/exposition.py
@@ -154,7 +154,7 @@ def _get_best_family(address, port):
     # binding an ipv6 address is requested.
     # This function is based on what upstream python did for http.server
     # in https://github.com/python/cpython/pull/11767
-    infos = socket.getaddrinfo(address, port)
+    infos = socket.getaddrinfo(address, port, type=socket.SOCK_STREAM, flags=socket.AI_PASSIVE)
     family, _, _, _, sockaddr = next(iter(infos))
     return family, sockaddr[0]
 
diff --git a/prometheus_client/metrics.py b/prometheus_client/metrics.py
index 34305a1..91cd9ec 100644
--- a/prometheus_client/metrics.py
+++ b/prometheus_client/metrics.py
@@ -292,6 +292,12 @@ class Counter(MetricWrapperBase):
         # Count only one type of exception
         with c.count_exceptions(ValueError):
             pass
+            
+    You can also reset the counter to zero in case your logical "process" restarts
+    without restarting the actual python process.
+
+       c.reset()
+
     """
     _type = 'counter'
 
@@ -310,6 +316,11 @@ class Counter(MetricWrapperBase):
             _validate_exemplar(exemplar)
             self._value.set_exemplar(Exemplar(exemplar, amount, time.time()))
 
+    def reset(self) -> None:
+        """Reset the counter to zero. Use this when a logical process restarts without restarting the actual python process."""
+        self._value.set(0)
+        self._created = time.time()
+
     def count_exceptions(self, exception: Union[Type[BaseException], Tuple[Type[BaseException], ...]] = Exception) -> ExceptionCounter:
         """Count exceptions in a block of code or function.
 


Test patch:
diff --git a/tests/test_core.py b/tests/test_core.py
index 6f7c9d1..30f9e0a 100644
--- a/tests/test_core.py
+++ b/tests/test_core.py
@@ -43,6 +43,16 @@ class TestCounter(unittest.TestCase):
         self.counter.inc(7)
         self.assertEqual(8, self.registry.get_sample_value('c_total'))
 
+    def test_reset(self):
+        self.counter.inc()
+        self.assertNotEqual(0, self.registry.get_sample_value('c_total'))
+        created = self.registry.get_sample_value('c_created')
+        time.sleep(0.05)
+        self.counter.reset()        
+        self.assertEqual(0, self.registry.get_sample_value('c_total'))
+        created_after_reset = self.registry.get_sample_value('c_created')
+        self.assertLess(created, created_after_reset)       
+
     def test_repr(self):
         self.assertEqual(repr(self.counter), "prometheus_client.metrics.Counter(c)")