Skip to content

Commit 4b00130

Browse files
authored
refactor(core): improve iOS log messages from stdout/stderr (#14385)
* refactor(core): improve iOS log messages from stdout/stderr move the stdout/stderr forward logic to Swift so it does not consume a Rust thread and never deadlocks on the simulator I had to work on this because i'm facing #12172 again on latest Xcode (26.1) * patch
1 parent 8e3bd63 commit 4b00130

File tree

5 files changed

+99
-31
lines changed

5 files changed

+99
-31
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"tauri": patch:bug
3+
"tauri-macros": patch:bug
4+
---
5+
6+
Fix iOS deadlock when running on the simulator from Xcode by properly piping stdout/stderr messages through the Xcode console and OSLog.

crates/tauri/mobile/ios-api/Sources/Tauri/Logger.swift

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,86 @@
44

55
import os.log
66
import UIKit
7+
import Foundation
8+
9+
class StdoutRedirector {
10+
private var originalStdout: Int32 = -1
11+
private var originalStderr: Int32 = -1
12+
private var stdoutPipe: [Int32] = [-1, -1]
13+
private var stderrPipe: [Int32] = [-1, -1]
14+
private var stdoutReadSource: DispatchSourceRead?
15+
private var stderrReadSource: DispatchSourceRead?
16+
17+
func start() {
18+
originalStdout = dup(STDOUT_FILENO)
19+
originalStderr = dup(STDERR_FILENO)
20+
21+
guard Darwin.pipe(&stdoutPipe) == 0,
22+
Darwin.pipe(&stderrPipe) == 0 else {
23+
Logger.error("Failed to create stdout/stderr pipes")
24+
return
25+
}
26+
27+
dup2(stdoutPipe[1], STDOUT_FILENO)
28+
dup2(stderrPipe[1], STDERR_FILENO)
29+
close(stdoutPipe[1])
30+
close(stderrPipe[1])
31+
32+
stdoutReadSource = createReader(
33+
readPipe: stdoutPipe[0],
34+
writeToOriginal: originalStdout,
35+
label: "stdout"
36+
)
37+
38+
stderrReadSource = createReader(
39+
readPipe: stderrPipe[0],
40+
writeToOriginal: originalStderr,
41+
label: "stderr"
42+
)
43+
}
44+
45+
private func createReader(
46+
readPipe: Int32,
47+
writeToOriginal: Int32,
48+
label: String
49+
) -> DispatchSourceRead {
50+
let source = DispatchSource.makeReadSource(
51+
fileDescriptor: readPipe,
52+
queue: .global(qos: .utility)
53+
)
54+
55+
source.setEventHandler {
56+
let bufferSize = 4096
57+
var buffer = [UInt8](repeating: 0, count: bufferSize)
58+
let bytesRead = read(readPipe, &buffer, bufferSize)
59+
60+
if bytesRead > 0 {
61+
let output = String(
62+
bytes: buffer[0..<bytesRead],
63+
encoding: .utf8
64+
) ?? ""
65+
66+
let trimmed = output.trimmingCharacters(in: .newlines)
67+
if !trimmed.isEmpty {
68+
// we're sending stderr to oslog, so we need to avoid recursive calls
69+
if trimmed.hasPrefix("OSLOG-") {
70+
// make sure the system can parse the oslogs
71+
write(writeToOriginal, &buffer, bytesRead)
72+
} else {
73+
Logger.info("[\(label)] \(trimmed)")
74+
}
75+
}
76+
}
77+
}
78+
79+
source.setCancelHandler {
80+
close(readPipe)
81+
}
82+
83+
source.resume()
84+
return source
85+
}
86+
}
787

888
/// Wrapper class for os_log function
989
public class Logger {
@@ -21,7 +101,7 @@ public class Logger {
21101
}
22102
}
23103

24-
static func log(_ items: Any..., category: String, type: OSLogType) {
104+
static func log(_ items: [Any], category: String, type: OSLogType) {
25105
if Logger.enabled {
26106
var message = ""
27107
let last = items.count - 1
@@ -31,6 +111,7 @@ public class Logger {
31111
message += " "
32112
}
33113
}
114+
34115
let log = OSLog(subsystem: Bundle.main.bundleIdentifier ?? "-", category: category)
35116
os_log("%{public}@", log: log, type: type, String(message.prefix(4068)))
36117
}

crates/tauri/mobile/ios-api/Sources/Tauri/Tauri.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,14 @@ extension PluginManager: NSCopying {
109109
}
110110
}
111111

112+
private var stdoutRedirector: StdoutRedirector?
113+
114+
@_cdecl("log_stdout")
115+
func logStdout() {
116+
stdoutRedirector = StdoutRedirector()
117+
stdoutRedirector!.start()
118+
}
119+
112120
@_cdecl("register_plugin")
113121
func registerPlugin(name: SRString, plugin: NSObject, config: SRString, webview: WKWebView?) {
114122
PluginManager.shared.load(

crates/tauri/src/ios.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,4 @@ swift!(pub fn register_plugin(
4646
webview: *const c_void
4747
));
4848
swift!(pub fn on_webview_created(webview: *const c_void, controller: *const c_void));
49+
swift!(pub fn log_stdout());

crates/tauri/src/lib.rs

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -248,38 +248,10 @@ pub const VERSION: &str = env!("CARGO_PKG_VERSION");
248248
#[cfg(target_os = "ios")]
249249
#[doc(hidden)]
250250
pub fn log_stdout() {
251-
use std::{
252-
ffi::CString,
253-
fs::File,
254-
io::{BufRead, BufReader},
255-
os::unix::prelude::*,
256-
thread,
257-
};
258-
259-
let mut logpipe: [RawFd; 2] = Default::default();
251+
#[cfg(target_os = "ios")]
260252
unsafe {
261-
libc::pipe(logpipe.as_mut_ptr());
262-
libc::dup2(logpipe[1], libc::STDOUT_FILENO);
263-
libc::dup2(logpipe[1], libc::STDERR_FILENO);
253+
crate::ios::log_stdout();
264254
}
265-
thread::spawn(move || unsafe {
266-
let file = File::from_raw_fd(logpipe[0]);
267-
let mut reader = BufReader::new(file);
268-
let mut buffer = String::new();
269-
loop {
270-
buffer.clear();
271-
if let Ok(len) = reader.read_line(&mut buffer) {
272-
if len == 0 {
273-
break;
274-
} else if let Ok(msg) = CString::new(buffer.as_bytes())
275-
.map_err(|_| ())
276-
.and_then(|c| c.into_string().map_err(|_| ()))
277-
{
278-
log::info!("{}", msg);
279-
}
280-
}
281-
}
282-
});
283255
}
284256

285257
/// The user event type.

0 commit comments

Comments
 (0)