-
Notifications
You must be signed in to change notification settings - Fork 17
Feed caching #212
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Feed caching #212
Changes from all commits
Commits
Show all changes
28 commits
Select commit
Hold shift + click to select a range
146c684
Cache `brokerd` feeds for reuse in clearing loop
goodboy a0660e5
Start top level cacheing apis module
goodboy 0ce8057
Move feed cacheing to cache mod; put entry retreival into ctx mng
goodboy 68ce5b3
Add lifo cache to new module; drop "utils", bleh
goodboy 66f1d91
Let's abstractify: ->
goodboy 7d5add1
Add an njs cache gist link
goodboy 224dbbc
Drop feed refs
goodboy a7d3afc
Add a `maybe_open_feed()` which uses new broadcast chans
goodboy 7d0f473
Use `maybe_open_feed()` in ems and fsp daemons
goodboy 2202abc
Add (lack of proper) ring buffer note
goodboy 310d8f4
Add disclaimer to old data mod
goodboy 954dc6a
Fix missing cache hit bool element of return
goodboy 71b50fd
Use broadcast chan for order client and avoid chan repacking
goodboy 0c95160
TO SQUASH cached ctx.
goodboy c8e3208
Add super basic support for data feed "pausing"
goodboy 2f5abaa
Add njs token bucket gist url
goodboy 1e42f58
Add pause/resume feed api, delegate to msg stream for broadcast api
goodboy fe0d66e
Drop removed module import
goodboy 2a9d24c
Remove dead OHLC index consumers from subs list on error
goodboy 2f1455d
Lol, don't use `maybe_open_feed()` for now, it's totally borked...
goodboy ff322ae
Re-impl ctx-mng caching using `trio.Nursery.start()`
goodboy cae7f48
Revert "Lol, don't use `maybe_open_feed()` for now, it's totally bork…
goodboy bbcce0c
Facepalm^2: pass through kwargs
goodboy 1184a4d
Cache sample step streams per actor
goodboy c368234
Use the actor's service nursery instead
goodboy 2df16e1
Re-implement client caching using `maybe_open_ctx`
goodboy 26cb7aa
Drop tractor stream shielding use
goodboy 4527d4a
Allocate an event per context
goodboy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,195 @@ | ||
| # piker: trading gear for hackers | ||
| # Copyright (C) Tyler Goodlet (in stewardship for piker0) | ||
|
|
||
| # This program is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU Affero General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
|
|
||
| # This program is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU Affero General Public License for more details. | ||
|
|
||
| # You should have received a copy of the GNU Affero General Public License | ||
| # along with this program. If not, see <https://www.gnu.org/licenses/>. | ||
|
|
||
| """ | ||
| Cacheing apis and toolz. | ||
|
|
||
| """ | ||
| # further examples of interest: | ||
| # https://gist.github.com/njsmith/cf6fc0a97f53865f2c671659c88c1798#file-cache-py-L8 | ||
|
|
||
| from collections import OrderedDict | ||
| from typing import ( | ||
| Any, | ||
| Hashable, | ||
| Optional, | ||
| TypeVar, | ||
| AsyncContextManager, | ||
| ) | ||
| from contextlib import ( | ||
| asynccontextmanager, | ||
| ) | ||
|
|
||
| import trio | ||
| from trio_typing import TaskStatus | ||
| import tractor | ||
|
|
||
| from .brokers import get_brokermod | ||
| from .log import get_logger | ||
|
|
||
|
|
||
| T = TypeVar('T') | ||
| log = get_logger(__name__) | ||
|
|
||
|
|
||
| def async_lifo_cache(maxsize=128): | ||
| """Async ``cache`` with a LIFO policy. | ||
|
|
||
| Implemented my own since no one else seems to have | ||
| a standard. I'll wait for the smarter people to come | ||
| up with one, but until then... | ||
| """ | ||
| cache = OrderedDict() | ||
|
|
||
| def decorator(fn): | ||
|
|
||
| async def wrapper(*args): | ||
| key = args | ||
| try: | ||
| return cache[key] | ||
| except KeyError: | ||
| if len(cache) >= maxsize: | ||
| # discard last added new entry | ||
| cache.popitem() | ||
|
|
||
| # do it | ||
| cache[key] = await fn(*args) | ||
| return cache[key] | ||
|
|
||
| return wrapper | ||
|
|
||
| return decorator | ||
|
|
||
|
|
||
| _cache: dict[str, 'Client'] = {} # noqa | ||
|
|
||
|
|
||
| class cache: | ||
| '''Globally (processs wide) cached, task access to a | ||
| kept-alive-while-in-use async resource. | ||
|
|
||
| ''' | ||
| lock = trio.Lock() | ||
| users: int = 0 | ||
| values: dict[Any, Any] = {} | ||
| resources: dict[ | ||
| int, | ||
| Optional[tuple[trio.Nursery, trio.Event]] | ||
| ] = {} | ||
| no_more_users: Optional[trio.Event] = None | ||
|
|
||
| @classmethod | ||
| async def run_ctx( | ||
| cls, | ||
| mng, | ||
| key, | ||
| task_status: TaskStatus[T] = trio.TASK_STATUS_IGNORED, | ||
|
|
||
| ) -> None: | ||
| async with mng as value: | ||
|
|
||
| _, no_more_users = cls.resources[id(mng)] | ||
| cls.values[key] = value | ||
| task_status.started(value) | ||
| try: | ||
| await no_more_users.wait() | ||
| finally: | ||
| value = cls.values.pop(key) | ||
| # discard nursery ref so it won't be re-used (an error) | ||
| cls.resources.pop(id(mng)) | ||
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def maybe_open_ctx( | ||
|
|
||
| key: Hashable, | ||
| mngr: AsyncContextManager[T], | ||
|
|
||
| ) -> (bool, T): | ||
| '''Maybe open a context manager if there is not already a cached | ||
| version for the provided ``key``. Return the cached instance on | ||
| a cache hit. | ||
|
|
||
| ''' | ||
|
|
||
| await cache.lock.acquire() | ||
|
|
||
| ctx_key = id(mngr) | ||
|
|
||
| value = None | ||
| try: | ||
| # lock feed acquisition around task racing / ``trio``'s | ||
| # scheduler protocol | ||
| value = cache.values[key] | ||
| log.info(f'Reusing cached resource for {key}') | ||
| cache.users += 1 | ||
| cache.lock.release() | ||
| yield True, value | ||
|
|
||
| except KeyError: | ||
| log.info(f'Allocating new feed for {key}') | ||
|
|
||
| # **critical section** that should prevent other tasks from | ||
| # checking the cache until complete otherwise the scheduler | ||
| # may switch and by accident we create more then one feed. | ||
|
|
||
| # TODO: avoid pulling from ``tractor`` internals and | ||
| # instead offer a "root nursery" in piker actors? | ||
| service_n = tractor.current_actor()._service_n | ||
|
|
||
| # TODO: does this need to be a tractor "root nursery"? | ||
| ln = cache.resources.get(ctx_key) | ||
| assert not ln | ||
|
|
||
| ln, _ = cache.resources[ctx_key] = (service_n, trio.Event()) | ||
|
|
||
| value = await ln.start(cache.run_ctx, mngr, key) | ||
| cache.users += 1 | ||
| cache.lock.release() | ||
|
|
||
| yield False, value | ||
|
|
||
| finally: | ||
| cache.users -= 1 | ||
|
|
||
| if cache.lock.locked(): | ||
| cache.lock.release() | ||
|
|
||
| if value is not None: | ||
| # if no more consumers, teardown the client | ||
| if cache.users <= 0: | ||
| log.warning(f'De-allocating resource for {key}') | ||
|
|
||
| # terminate mngr nursery | ||
| _, no_more_users = cache.resources[ctx_key] | ||
| no_more_users.set() | ||
|
|
||
|
|
||
| @asynccontextmanager | ||
| async def open_cached_client( | ||
| brokername: str, | ||
| ) -> 'Client': # noqa | ||
| '''Get a cached broker client from the current actor's local vars. | ||
|
|
||
| If one has not been setup do it and cache it. | ||
|
|
||
| ''' | ||
| brokermod = get_brokermod(brokername) | ||
| async with maybe_open_ctx( | ||
| key=brokername, | ||
| mngr=brokermod.get_client(), | ||
| ) as (cache_hit, client): | ||
| yield client | ||
This file was deleted.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
not bad right?