Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# CHANGELOG

## 96.0.0

* BREAKING CHANGE: the `gunicorn_defaults` module has been moved to `gunicorn.defaults` to make space for gunicorn-related utils that don't have the restricted-import constraints of the gunicorn defaults. Imports of `notifications_utils.gunicorn_defaults` should be changed to `notifications_utils.gunicorn.defaults`.
* Added `ContextRecyclingEventletWorker` custom gunicorn worker class.

## 95.2.0

* Implement `InsensitiveSet.__contains__`
Expand Down
3 changes: 3 additions & 0 deletions notifications_utils/gunicorn/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Avoid additions to this file as it will implicitly be imported before all
# notifications_utils.gunicorn.* imports and we need to be able to use the
# defaults module from an early stage of app bringup
34 changes: 34 additions & 0 deletions notifications_utils/gunicorn/eventlet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import contextvars
from collections import deque

import greenlet
from gunicorn.workers import geventlet


class ContextRecyclingEventletWorker(geventlet.EventletWorker):
"""
Because eventlet's GreenPool discards GreenThreads after they have performed a task,
to reduce wasteful continual re-creation of thread-local resources this class will
maintain a pool of thread contexts suitable for reuse with new GreenThreads. In
theory at least this means we will never have more thread contexts than the maximum
number of concurrent GreenThreads handling connections we've ever had.
"""

def __init__(self, *args, **kwargs):
self.context_pool = deque() # a stack of unused thread contexts
super().__init__(*args, **kwargs)

def handle(self, *args, **kwargs):
g = greenlet.getcurrent()
if self.context_pool:
# reuse an existing thread context from the pool
g.gr_context = self.context_pool.pop()

ret = super().handle(*args, **kwargs)

# stash potentially-populated thread context in context_pool
self.context_pool.append(g.gr_context)
# replace reference to now-stashed context with an empty one
g.gr_context = contextvars.Context()

return ret
2 changes: 1 addition & 1 deletion notifications_utils/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# - `make version-minor` for new features
# - `make version-patch` for bug fixes

__version__ = "95.2.0" # e423953f9dec74010897c62aa4e035de
__version__ = "96.0.0" # 6fdd72884bdeadbeef