tauri icon indicating copy to clipboard operation
tauri copied to clipboard

High memory usage when invoking commands [bug]

Open twharmon opened this issue 2 years ago • 52 comments

Describe the bug

High memory usage when sending lots of commands for rust from js.

Reproduction

Invoke a command in an interval:

setInterval(() => {
  invoke<number[][]>('get_data', args).then(drawOnHtmlCanvasElement)
}, 50)

Return large amount of data from rust to js:

#[tauri::command]
fn get_data(path: &str, start: u64, length: usize, filters: Vec<FilterConfig>) -> Vec<Vec<f32>> {
    vec![vec![0.0; 2560]; 21]
    // data::get(path, start, length, filters)
}

After several thousand invocations, watch memory usage creep up: image

Eventually, when memory pressure is sufficient, the usage plateaus. It appears that garbage collection runs regularly, reducing the memory usage of both the processes that hog memory.

Expected behavior

I would expect the app to run without hogging memory resources.

Platform and versions

> [email protected] tauri
> tauri "info"


Environment
  › OS: Mac OS 12.2.1 X64
  › Node.js: 16.13.1
  › npm: 8.1.2
  › pnpm: Not installed!
  › yarn: Not installed!
  › rustup: 1.24.3
  › rustc: 1.57.0
  › cargo: 1.57.0
  › Rust toolchain: stable-aarch64-apple-darwin 

Packages
  › @tauri-apps/cli [NPM]: 1.0.0-rc.9
  › @tauri-apps/api [NPM]: 1.0.0-rc.4
  › tauri [RUST]: 1.0.0-rc.8,
  › tauri-build [RUST]: 1.0.0-rc.7,
  › tao [RUST]: 0.8.3,
  › wry [RUST]: 0.15.1,

App
  › build-type: bundle
  › CSP: unset
  › distDir: ../build
  › devPath: http://localhost:3000/
  › framework: React

App directory structure
  ├─ node_modules
  ├─ public
  ├─ src-tauri
  ├─ build
  ├─ .git
  └─ src

Stack trace

N/A

Additional context

The get_data command only needs 5ms on my machine. That means the interval is not running too fast, since it has > 40ms of rest time between invocations.

twharmon avatar May 02 '22 15:05 twharmon

I feel this is the same problem as #3921, @wusyong can confirm.

lucasfernog avatar May 02 '22 15:05 lucasfernog

Oh didn't see you closed it. When we talked about it on discord i was able to reproduce this on windows too, although it felt less severe. I'm gonna test it again later today with this specific repro.

Edit: Thought about this issue again because of #4038 btw

FabianLars avatar May 03 '22 09:05 FabianLars

From what @wusyong said all platform webviews have some kind of memory leak issue (webview2 has it even in the custom protocol stuff).

lucasfernog avatar May 03 '22 13:05 lucasfernog

Does it leak "in" the rust process or one of the webview ones, because on windows it was the rust process that had increasing memory usage (well, the webview did too but idc about that).

Buuut it wasn't that severe iirc, like i couldn't get it to 12gigs like in the screenshot. Will re-test later or tomorrow anyway just to be sure.

Oh and now that i think about it, opening a file picker dialog increases the rust's process memory usage by 100mb and for whatever reason this won't get cleaned up completely until you exit the app. (Subsequent dialogs don't increase them that much so i guess this is just how it works, memory is meant to be used after all, so i guess it's windows itself not cleaning up if it doesn't see it as necessary?)

FabianLars avatar May 03 '22 14:05 FabianLars

The Linux leak was in the webkit process itself. I didn't test the macOS one yet.

lucasfernog avatar May 03 '22 15:05 lucasfernog

In my test for the HTTP memory leak the Rust process didn't increase its memory usage, but the webview2 one did (a lot), in less than a minute it was using an extra 300MB.

lucasfernog avatar May 03 '22 16:05 lucasfernog

Do y'all have one or more upstream issues to track? Memory leaks are nasty, and can cover up more leaks being added. Rust is not immune to it either, it's fairly easy to accidentally create ref cycles. I've had some experience tracking those down so let me know if you're interested in techniques.

Also, workarounds can help. For instance in https://github.com/MicrosoftEdge/WebView2Feedback/issues/1633 it appears to only accumulate when the webview is attached, so if closing + creating new window is better than hide + show, it's worth knowing as a Tauri user.

betamos avatar May 03 '22 18:05 betamos

@lucasfernog are you sure this is not like #852 ? When looking at dangling references to leaked http response objects it looked like they were from some closures originating in cmd invoke wrappers but I haven't looked at not minimized code.

qu1ck avatar May 03 '22 23:05 qu1ck

Do y'all have one or more upstream issues to track? Memory leaks are nasty, and can cover up more leaks being added. Rust is not immune to it either, it's fairly easy to accidentally create ref cycles. I've had some experience tracking those down so let me know if you're interested in techniques.

Also, workarounds can help. For instance in MicrosoftEdge/WebView2Feedback#1633 it appears to only accumulate when the webview is attached, so if closing + creating new window is better than hide + show, it's worth knowing as a Tauri user.

The one i'm currently tracking is https://github.com/MicrosoftEdge/WebView2Feedback/issues/2171 not sure about webkit issues yet.

lucasfernog avatar May 04 '22 14:05 lucasfernog

@lucasfernog are you sure this is not like #852 ? When looking at dangling references to leaked http response objects it looked like they were from some closures originating in cmd invoke wrappers but I haven't looked at not minimized code.

I could reproduce the issue with only the eval function:

use tauri::Manager;

fn main() {
  tauri::Builder::default()
    .setup(|app| {
      let w = app.get_window("main").unwrap();
      std::thread::spawn(move || loop {
        std::thread::sleep_ms(500);
        let v = vec![0xff; 1024 * 1024 * 2];
        w.eval(&format!("{:?}", v)).unwrap();
      });
      Ok(())
    })
    .run(tauri::generate_context!())
    .expect("error while running tauri application");
}

seems like the webview doesn't handle it very well.

lucasfernog avatar May 04 '22 14:05 lucasfernog

It also memory leak on Windows.

lzdyes avatar May 07 '22 09:05 lzdyes

@lucasfernog I tried your suggestion in https://github.com/tauri-apps/tauri/issues/3921#issuecomment-1115089647 to use http server as bypass invoke system. It does NOT help, memory for http response objects is still leaking. If I understand correctly this completely bypasses the rust->webview messaging and should not have the same root cause as the webview bug linked above.

Am I missing something or are there 2 separate issues here?

Minimal repro attached: httpleakrepro.zip

qu1ck avatar May 15 '22 18:05 qu1ck

Also, a memory leak happens when you emit an event to a window in a loop.

During my investigation, I found out that if you emit an event to the window but on the window, there is no listener registered there will be no memory leak. But if a listener is registered, a memory leak occurs.

ahkohd avatar May 17 '22 19:05 ahkohd

Ok, I'm fairly certain now that I have proof of memory leak in tauri code, not webview.

I modified the repro I linked in comment above. Now it has 2 http fetch variants: they both go through localhost server but one is via tauri invoke system and another is directly proxying requests. First one leaks memory while the second one doesn't. httpleakrepro.zip

qu1ck avatar May 18 '22 22:05 qu1ck

The leak happening in the tauri code doesn't mean it is not a webview leak, the invoke system use webview APIs (we already traced the issue to the eval function, which the invoke system uses).

lucasfernog avatar May 19 '22 17:05 lucasfernog

Does tauri-invoke-http-like system also use eval somewhere? Because if not then we are dealing with 2 memory leaks.

qu1ck avatar May 19 '22 18:05 qu1ck

Found something: https://bugs.webkit.org/show_bug.cgi?id=215729 https://github.com/Danesz/Sidewalk

ahkohd avatar May 19 '22 18:05 ahkohd

Since we know we have a memory leak when calling eval, I will suggest that we should update the invoke system to use WebSockets to create some kind of RPC system.

Thinking out loud.

ahkohd avatar May 19 '22 18:05 ahkohd

tauri-invoke-http doesn't use eval - it uses a localhost server.

lucasfernog avatar May 19 '22 18:05 lucasfernog

tauri-invoke-http doesn't use eval - it uses a localhost server.

Can we upgrade this to use WebSocket instead of HTTP? Maybe I fork the repo and see what I can do.

ahkohd avatar May 19 '22 18:05 ahkohd

Hey just copy it and create tauri-invoke-ws :joy: should be easy

lucasfernog avatar May 19 '22 18:05 lucasfernog

tauri-invoke-http doesn't use eval - it uses a localhost server.

And it leaks memory. So, like I said, 2 independent memory leaks. See my last attachment in https://github.com/tauri-apps/tauri/issues/4026#issuecomment-1130633129

qu1ck avatar May 19 '22 18:05 qu1ck

If you're using Windows it's the https://github.com/MicrosoftEdge/WebView2Feedback/issues/2171 issue. webkit might have a similar problem too, i didn't check it yet.

lucasfernog avatar May 19 '22 18:05 lucasfernog

@qu1ck use https://github.com/ahkohd/tauri-awesome-rpc

Works like charm.

Also avoid using tauri APIs that uses eval under the hood such as window.emit(). Use AwesomeEmit and AwesomeEvent instead. See the repo for usage docs.

ahkohd avatar May 21 '22 20:05 ahkohd

@ahkohd your invoke implementation does not help.

image

Lucas is claiming that it's yet another issue not related to eval leak. It's not obvious to me how c++/rust land streams have effect on javascript objects that were created entirely in javascript but here we are.

qu1ck avatar May 21 '22 21:05 qu1ck

I'm on OSX. Using the WS RPC, I have seen a significant memory usage reduction. It seems there is another cause of your memory leak.

ahkohd avatar May 21 '22 22:05 ahkohd

@qu1ck Yeah it seems some memory leaks still exist, but I can't really pinpoint what is causing them.

ahkohd avatar May 23 '22 20:05 ahkohd

#4274 doesn't fix this issue, but it helps a little bit at least with a problem on our side.

lucasfernog avatar Jun 05 '22 12:06 lucasfernog

I I think I am experiencing the same bug as well on windows. In my case the rust part sends events at a very high rate 20K events/second. Then these events are rendered using chartjs. The memory consumption grows at about 5Mb per second and the application cannot stay up for more then few minutes.

iilyak avatar Jun 06 '22 00:06 iilyak

#4274 doesn't fix this issue, but it helps a little bit at least with a problem on our side.

I think, there is another problem in wry (wkwebview mod). Rust calls OC using objc crate, an NSString (which is the invoke response data that will be posted to webview via eval method) allocs in rust and never be released in the correct way so that memory usage keeps increasing in the Tauri process.

f91kdash avatar Jun 06 '22 08:06 f91kdash