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
Feature request: heap-allocated alerts #5154
Comments
thank you for a well made argument. I felt uneasy introducing this memory allocation restriction as well, although, previously alerts were held by I'm a bit hesitant to go all the way to individually heap allocate alerts. That can actually be quite inefficient. The argument about clients already are encouraged to limit alerts by I imagine you've seen this blog post, detailing how the alert queue works now. It uses two separate allocations for the alert objects themselves and the data they carry. This makes it especially tricky to allow cloning of them. Sure, the alert object itself is easy to make a copy of, but the data is still owned by another stack allocator. In order to support your use case, and more broadly, allowing deferring handling alerts until after another call to
|
Thanks for the well thought out response!
I agree, this is a crucial case. It's interesting to think though that a python app likely does dozens of allocations when processing each alert, even if it's just logging most of them. So optimizing alert allocations helps throughput in libtorrent threads, but the app may get bogged down regardless.
Nice thought. I think it would actually reduce complexity, since alert handling code could get away with doing blocking calls while handling alerts, and leave it to user configuration (or automagic app code) to increase buffers if alert handling gets too backed up.
This sounds like the best of both worlds. Fast allocation for libtorrent, but we still have (basically) refcounted alerts, so no buffer configuration to manage and "unexpected" references don't crash the app. Seems like an improvement over (1). Is this just called slab allocation?
This also has the upsides of (2), but I guess it's more code maintenance since every alert type needs a copy method. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
I'm still very interested in this. Do you foresee having time to prototype your suggestion about "detaching" the alerts allocation slab? |
this is still on my todo list, it's not particularly urgent compared to some other things on there though |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Go away @Stale :) |
@arvidn is there any way I can keep this open despite the stale bot? |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
@arvidn perhaps the stale bot config should ignore the Otherwise if you don't think you can attempt the "detachable queue" solution in the foreseeable future, perhaps this should just be closed. I care about this issue a lot but perhaps I'm the only one who cares about it. The work is definitely beyond my skill to do myself. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
TL;DR
I'd like to request that alerts be allocated on the heap, or have the option to be. This is to support concurrency, simplicity and safety of client code.
I assume the main advantage of the specialized memory allocation is speed of the main libtorrent thread. I'd like to hear if there are any others.
I have a lot of thoughts on why the specialized alert memory management is a disadvantage. Apologies for the wall of text; it's a significant change so I thought it important to be thorough.
Argument summary
pop_alerts()
can be called ASAP). This can lead to awkward workarounds in code trying to avoid calls totorrent_handle::status()
etc.alert_mask
as much as possible, which should minimize the allocations done in the libtorrent threadI'll try to motivate my request by explaining my use case, and exploring the limits of how I can use libtorrent to implement my use case, and how those limits relate to alert memory management.
My use case
The goal of my project tvaf is to provide access to a library of torrents as HTTP resources, downloading data on the fly. It must be highly responsive and concurrent.
This leads to a pretty complex workflow:
Keeping in mind that new requests may come in or be canceled at any point in the workflow.
Event loop vs iterators
Every libtorrent-based app I can find uses a single-threaded event loop which fires a set of callbacks on user action or alert from libtorrent.
In my experience, single-threaded event loop apps either
Code-wise, the disadvantage of event loops is that they turn control flow into lots of state variables and callbacks. This isn't appropriate for all use cases.
For example, consider that in tvaf I want to gracefully pause a torrent and delete it if it has no data.
With a single-threaded event loop, that looks something like this
It's pathological in Python, due to the lack of anonymous inline functions. Callbacks mean the second half of our code reads first.
Note also the blocking
status()
call in an alert handler. How do we deal with that?Consider an iterator (or "channel") pattern:
This is far easier to understand. In practice, I am finding the complexity of iterator code increases much less quickly than event loop code.
The iterator code is also very friendly to concurrency. The torrent may be "unexpectedly" removed at any point; the code will simply raise an exception which can be handled or ignored. The blocking
status()
call occurs outside of the alert-handling loop, so it need not block other alert-handling code if we play our cards right.Implementing iterators
I think the reason most app authors don't use the iterator pattern above is that it's not straightforward to implement
iter_alerts()
. Normally, it would just return a queue of alerts, and a feeder thread would callpop_alerts()
and feed all the queues. Butpop_alerts()
invalidates existing alerts, so iterator consumers quickly touch invalid memory.The trick is that the feeder thread must wait until all consumers are blocked waiting for new alerts; then it's safe to call
pop_alerts()
and feed them all.To avoid thundering herds, I have
iter_alerts()
take some arguments to filter the alert stream by type and handle.I implemented these iterators in a recent commit to tvaf.
Iterator quirks
Due to alert memory management, we still want to avoid blocking calls while handling alerts to maximize responsiveness and concurrency.
This can lead to some weird code. Instead of this:
I might try to get status data from the alert stream, like this:
In Python, there's an additional trick that iterators aren't explicitly signaled when consumer code abandons them due to an exception or
break
. We can detect when they're garbage collected, but this may not be immediate. I thought it was better to use a context manager like so:Alert references
Consider this:
In the above code,
exc
holds a stack trace, which refers to the stack frame in which it occurred, which refers to the local variablealert
. So thealert
gets kept alive as long as we hold the exception.Or this:
In some conditions, I have seen the logger hold a reference to the
alert
argument after the call completes (potentially for the lifetime of the app).I think Python is just generally not designed for "specialized" memory management, where it's important to positively release references.
My specific fear is either that some code will end up calling
str(alert)
, or that the python bindings may change such thatrepr(alert)
touches alert memory where it didn't before. I dislike the thought of my app crashing due to conditions that can't be guaranteed against.The text was updated successfully, but these errors were encountered: