11use std:: {
22 collections:: VecDeque ,
33 io:: { Read , Write } ,
4+ mem:: ManuallyDrop ,
45 sync:: { Arc , Mutex , OnceLock } ,
56 thread,
67} ;
@@ -12,12 +13,19 @@ use crate::geo::ScreenSize;
1213
1314type ChildWaitResult = Result < ExitStatus , Arc < std:: io:: Error > > ;
1415
16+ // On musl libc (Alpine Linux), concurrent PTY operations trigger
17+ // SIGSEGV/SIGBUS in musl internals (sysconf, fcntl). This affects
18+ // openpty+fork, FD cleanup (close), and FD drops from any thread.
19+ // Serialize all PTY lifecycle operations that touch musl internals.
20+ #[ cfg( target_env = "musl" ) ]
21+ static PTY_LOCK : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
22+
1523/// The read half of a PTY connection. Implements [`Read`].
1624///
1725/// Reading feeds data through an internal vt100 parser (shared with [`PtyWriter`]),
1826/// keeping `screen_contents()` up-to-date with parsed terminal output.
1927pub struct PtyReader {
20- reader : Box < dyn Read + Send > ,
28+ reader : ManuallyDrop < Box < dyn Read + Send > > ,
2129 parser : Arc < Mutex < vt100:: Parser < Vt100Callbacks > > > ,
2230}
2331
@@ -28,7 +36,25 @@ pub struct PtyReader {
2836pub struct PtyWriter {
2937 writer : Arc < Mutex < Option < Box < dyn Write + Send > > > > ,
3038 parser : Arc < Mutex < vt100:: Parser < Vt100Callbacks > > > ,
31- master : Box < dyn MasterPty + Send > ,
39+ master : ManuallyDrop < Box < dyn MasterPty + Send > > ,
40+ }
41+
42+ impl Drop for PtyReader {
43+ fn drop ( & mut self ) {
44+ #[ cfg( target_env = "musl" ) ]
45+ let _guard = PTY_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
46+ // SAFETY: called exactly once, from drop.
47+ unsafe { ManuallyDrop :: drop ( & mut self . reader ) } ;
48+ }
49+ }
50+
51+ impl Drop for PtyWriter {
52+ fn drop ( & mut self ) {
53+ #[ cfg( target_env = "musl" ) ]
54+ let _guard = PTY_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
55+ // SAFETY: called exactly once, from drop.
56+ unsafe { ManuallyDrop :: drop ( & mut self . master ) } ;
57+ }
3258}
3359
3460/// A cloneable handle to a child process spawned in a PTY.
@@ -256,12 +282,6 @@ impl Terminal {
256282 ///
257283 /// Panics if the writer lock is poisoned when the background thread closes it.
258284 pub fn spawn ( size : ScreenSize , cmd : CommandBuilder ) -> anyhow:: Result < Self > {
259- // On musl libc (Alpine Linux), concurrent PTY operations trigger
260- // SIGSEGV/SIGBUS in musl internals (sysconf, fcntl). This affects
261- // both openpty+fork and FD cleanup (close) from background threads.
262- // Serialize all PTY lifecycle operations that touch musl internals.
263- #[ cfg( target_env = "musl" ) ]
264- static PTY_LOCK : std:: sync:: Mutex < ( ) > = std:: sync:: Mutex :: new ( ( ) ) ;
265285 #[ cfg( target_env = "musl" ) ]
266286 let _spawn_guard = PTY_LOCK . lock ( ) . unwrap_or_else ( |e| e. into_inner ( ) ) ;
267287
@@ -316,8 +336,11 @@ impl Terminal {
316336 ) ) ) ;
317337
318338 Ok ( Self {
319- pty_reader : PtyReader { reader, parser : Arc :: clone ( & parser) } ,
320- pty_writer : PtyWriter { writer, parser, master } ,
339+ pty_reader : PtyReader {
340+ reader : ManuallyDrop :: new ( reader) ,
341+ parser : Arc :: clone ( & parser) ,
342+ } ,
343+ pty_writer : PtyWriter { writer, parser, master : ManuallyDrop :: new ( master) } ,
321344 child_handle : ChildHandle { child_killer, exit_status } ,
322345 } )
323346 }
0 commit comments