fix: lock Noble.setup so concurrent first requests fetch the Go-lib once
Noble.setup guarded the one-time Go shared-library fetch with a bare 'if self._updated' flag — a TOCTOU race where concurrent first requests both passed the check before either set the flag, running the download multiple times. now guarded by a per-instance asyncio.Lock with a check-lock-recheck. verified under load: 2/10/100/500 concurrent setups run the fetch exactly once each (a no-lock control runs it N times). Signed-off-by: disqualifier <dev@disqualifier.me>
This commit is contained in:
parent
7ea8ecf888
commit
ae4c653ecc
16
README.md
16
README.md
@ -22,17 +22,17 @@ you want; importing the package never fails because an extra is missing.
|
||||
`requirements.txt` (pick the extra you need):
|
||||
|
||||
```
|
||||
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0
|
||||
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0
|
||||
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0
|
||||
aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1
|
||||
aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1
|
||||
aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1
|
||||
```
|
||||
|
||||
Direct:
|
||||
|
||||
```bash
|
||||
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0"
|
||||
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0"
|
||||
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.0"
|
||||
pip install "aioweb_tls[curl] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1"
|
||||
pip install "aioweb_tls[noble] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1"
|
||||
pip install "aioweb_tls[all] @ git+ssh://git@git.rethinkstudios.io/rethink-public/aioweb_tls.git@v0.1.1"
|
||||
```
|
||||
|
||||
- `[curl]` → curl_cffi backend · `[noble]` → noble_tls backend · `[all]` → both.
|
||||
@ -73,8 +73,8 @@ async with TLSSession(backend=Noble(client="chrome_133")) as s:
|
||||
|
||||
- `Noble(client="chrome_133")` — accepts a `noble_tls.Client` enum or a string name.
|
||||
- noble_tls downloads a Go shared library on first use. `await s.setup()` fetches it
|
||||
once at startup; if you skip it, the first request fetches it lazily (guarded to run
|
||||
once).
|
||||
once at startup; if you skip it, the first request fetches it lazily. The fetch is
|
||||
guarded by a lock, so even concurrent first requests download it exactly once.
|
||||
|
||||
## Writing your own backend (the `TLSBackend` protocol)
|
||||
|
||||
|
||||
@ -11,6 +11,7 @@ is not installed raises a clear RuntimeError naming the extra to install. import
|
||||
this module never fails because an extra is missing.
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import logging
|
||||
import math
|
||||
|
||||
@ -136,6 +137,7 @@ class Noble:
|
||||
) from _NOBLE_ERROR
|
||||
self.client = self._resolve_client(client)
|
||||
self._updated = False
|
||||
self._setup_lock = asyncio.Lock()
|
||||
|
||||
@staticmethod
|
||||
def _resolve_client(client):
|
||||
@ -145,12 +147,19 @@ class Noble:
|
||||
return client
|
||||
|
||||
async def setup(self) -> None:
|
||||
"""fetch the noble_tls Go shared library once; idempotent
|
||||
"""fetch the noble_tls Go shared library once; idempotent and concurrency-safe
|
||||
|
||||
download_if_necessary handles the first-time fetch (no lib present);
|
||||
update_if_necessary refreshes an existing one. try download first so a
|
||||
clean environment works, falling back to update.
|
||||
|
||||
guarded by an asyncio.Lock with a check-lock-recheck so concurrent first
|
||||
requests don't both run the fetch: the fast path returns once _updated is
|
||||
set, and only the first caller through the lock does the work.
|
||||
"""
|
||||
if self._updated:
|
||||
return
|
||||
async with self._setup_lock:
|
||||
if self._updated:
|
||||
return
|
||||
download = getattr(noble_tls, "download_if_necessary", None)
|
||||
|
||||
Loading…
Reference in New Issue
Block a user