781flyingdutchman / background_downloader

Flutter plugin for file downloads and uploads
Other
158 stars 73 forks source link

Limit number of concurrent uploads #171

Closed lyio closed 11 months ago

lyio commented 11 months ago

Is your feature request related to a problem? Please describe. Enqueuing a large(ish) number of uploads in quick succession, runs all the uploads concurrently. We have users of our App routinely trying to upload 60+ files. At some point we either run into memory limits (i.e. the App crashes) or the some of the requests simply time out.

Describe the solution you'd like I'm looking for a way to queue the uploads and ensure that only 3-5 are running concurrently.

Describe alternatives you've considered

Additional context This seems to only be a problem on iOS. Android handles this properly, apparently.

781flyingdutchman commented 11 months ago

Please take a look at issue #153 as I suspect the issue is more likely related to how you enqueue a large number of tasks. The OS manages task concurency and there isn't an easy way to affect that.

lyio commented 11 months ago

Using a for in loop to enqueue the uploads instead of Future.wait already fixed my immediate problem of the App crashing.

But the issue that iOS/the plugin does not actually schedule the uploads, but starts all of them right away cannot be solved by this, right?

So I'd have to build some sort of batching of jobs for our App myself.

781flyingdutchman commented 11 months ago

The plugin does schedule the tasks and is not responsible for the decision when a task is actually started - the OS is (except Desktop, see below), and in practice it won't run more than a handful of tasks in parallel. The OS manages concurrency, such that the overall system is not overloaded. For example, the number of concurrent tasks depends on the number of CPU cores, foreground and other background activity, battery state, internet connection availability and so forth. It's not something you can or should try to outsmart, as you won't have the same information as the OS has available to make those decisions. If you have other reasons why you want to limit the number of concurrent tasks (e.g. limit the number of concurrent connections to the same server) then you'll have to create a queue that only enqueues the next task when there are fewer than x already running. You can see how this can be done in the desktop_downloader.dart file - it's exactly that mechanism that's used for desktop because it doesn't have as sophisticated task management built in as mobile OSes do. Good luck!

lyio commented 11 months ago

Thank you for your response.

The queueing in the desktop implementation looks exactly like what I need. However, since it is run in Dart, it will only work on mobile as long as the App is actually running.

When the App is moved to the background, the queue management is removed, so it'd have to be implemented natively to work properly.

781flyingdutchman commented 11 months ago

Can you elaborate on why your need to add your own scheduling constraint/mechanism on top of what the OS provides?

781flyingdutchman commented 11 months ago

@lyio I've added the concept of a TaskQueue that sits 'in front of' the FileDownloader, and allows you to manage things like the number of concurrent tasks etc. See here. To your point, this doesn't fully solve the problem as it runs in Dart, not native, but it may be worth taking a look. Implementing this natively would require native persistent storage and I'm concerned that creates a lot of bloat and complexity, with likely still subpar results, as you're competing with the native queue management provided by WorkManager and UrlSessions. I therefore do not plan to go down that route.

lyio commented 11 months ago

Can you elaborate on why your need to add your own scheduling constraint/mechanism on top of what the OS provides?

Well, because I get the feeling that iOS in particular is doing a bad job of scheduling the tasks. The OS seems to just enqueue all of them at once. When I run the uploads through a proxy, I can see all of them popping up as I enqueue them. And the first couple of files always time out after a couple of minutes.

That is why I want to find a way that can handle this case better. I will check out the TaskQueue

lyio commented 11 months ago

@781flyingdutchman I like the TaskQueue 👍

Could you adjust the signature of taskFinished to allow Future<void> as well? I need to perform some asynchronous tasks here.

781flyingdutchman commented 11 months ago

Can't you just do that truly asynchronously, i.e. using an unawaited Future? I would rather not have to await the call to taskFinished and I'd also rather not change the signature (the only non breaking option is to have it return dynamic, but we'd not be awaiting it anyway).