88use std:: collections:: HashMap ;
99use std:: future;
1010use std:: pin:: Pin ;
11+ use std:: sync:: RwLock ;
1112
1213use anyhow:: anyhow;
1314use futures:: StreamExt ;
@@ -34,17 +35,20 @@ use crate::lsp::state_handlers::ConsoleInputs;
3435pub ( crate ) type TokioUnboundedSender < T > = tokio:: sync:: mpsc:: UnboundedSender < T > ;
3536pub ( crate ) type TokioUnboundedReceiver < T > = tokio:: sync:: mpsc:: UnboundedReceiver < T > ;
3637
37- // The global instance of the auxiliary event channel, used for sending log
38- // messages or spawning threads from free functions. Since this is an unbounded
39- // channel, sending a log message is not async nor blocking. Tokio senders are
40- // Send and Sync so this global variable can be safely shared across threads.
41- //
42- // Note that in case of duplicate LSP sessions (see
43- // https://github.com/posit-dev/ark/issues/622 and
44- // https://github.com/posit-dev/positron/issues/5321), it's possible for older
45- // LSPs to send log messages and tasks to the newer LSPs.
46- static mut AUXILIARY_EVENT_TX : std:: cell:: OnceCell < TokioUnboundedSender < AuxiliaryEvent > > =
47- std:: cell:: OnceCell :: new ( ) ;
38+ /// The global instance of the auxiliary event channel, used for sending log messages or
39+ /// spawning threads from free functions. Since this is an unbounded channel, sending a
40+ /// log message is not async nor blocking. Tokio senders are Send and Sync so this global
41+ /// variable can be safely shared across threads.
42+ ///
43+ /// LSP sessions can be restarted or reconnected at any time, which is why this is an
44+ /// `RwLock`, but we expect that to be very rare. Read locking is not expected to be
45+ /// contentious.
46+ ///
47+ /// Note that in case of duplicate LSP sessions (see
48+ /// https://github.com/posit-dev/ark/issues/622 and
49+ /// https://github.com/posit-dev/positron/issues/5321), it's possible for older
50+ /// LSPs to send log messages and tasks to the newer LSPs.
51+ static AUXILIARY_EVENT_TX : RwLock < Option < TokioUnboundedSender < AuxiliaryEvent > > > = RwLock :: new ( None ) ;
4852
4953// This is the syntax for trait aliases until an official one is stabilised.
5054// This alias is for the future of a `JoinHandle<anyhow::Result<T>>`
@@ -400,17 +404,16 @@ impl AuxiliaryState {
400404 // Channels for communication with the auxiliary loop
401405 let ( auxiliary_event_tx, auxiliary_event_rx) = tokio_unbounded_channel :: < AuxiliaryEvent > ( ) ;
402406
403- // Set global instance of this channel. This is used for interacting
404- // with the auxiliary loop (logging messages or spawning a task) from
405- // free functions.
406- unsafe {
407- if let Some ( val) = AUXILIARY_EVENT_TX . get_mut ( ) {
408- // Reset channel if already set. Happens e.g. on reconnection after a refresh.
409- * val = auxiliary_event_tx;
410- } else {
411- // Set channel for the first time
412- AUXILIARY_EVENT_TX . set ( auxiliary_event_tx) . unwrap ( ) ;
413- }
407+ // Set global instance of this channel. This is used for interacting with the
408+ // auxiliary loop (logging messages or spawning a task) from free functions.
409+ // Unfortunately this can theoretically be reset at any time, i.e. on reconnection
410+ // after a refresh, which is why we need an RwLock. This is the only place we take
411+ // a write lock though. We panic if we can't access the write lock, as that implies
412+ // the auxiliary loop has gone down and something is very wrong. We hold the lock
413+ // for as short as possible, hence the extra scope.
414+ {
415+ let mut tx = AUXILIARY_EVENT_TX . write ( ) . unwrap ( ) ;
416+ * tx = Some ( auxiliary_event_tx) ;
414417 }
415418
416419 // List of pending tasks for which we manage the lifecycle (mainly relay
@@ -487,17 +490,30 @@ impl AuxiliaryState {
487490 }
488491}
489492
490- fn auxiliary_tx ( ) -> & ' static TokioUnboundedSender < AuxiliaryEvent > {
493+ fn with_auxiliary_tx < F , T > ( f : F ) -> T
494+ where
495+ F : FnOnce ( & TokioUnboundedSender < AuxiliaryEvent > ) -> T ,
496+ {
497+ let auxiliary_event_tx = AUXILIARY_EVENT_TX
498+ . read ( )
499+ . expect ( "Can lock auxiliary event sender." ) ;
500+
491501 // If we get here that means the LSP was initialised at least once. The
492502 // channel might be closed if the LSP was dropped, but it should exist.
493- unsafe { AUXILIARY_EVENT_TX . get ( ) . unwrap ( ) }
503+ let auxiliary_event_tx = auxiliary_event_tx
504+ . as_ref ( )
505+ . expect ( "LSP should have been initialized at least once by now." ) ;
506+
507+ f ( auxiliary_event_tx)
494508}
495509
496510fn send_auxiliary ( event : AuxiliaryEvent ) {
497- if let Err ( err) = auxiliary_tx ( ) . send ( event) {
498- // The error includes the event
499- log:: warn!( "LSP is shut down, can't send event:\n {err:?}" ) ;
500- }
511+ with_auxiliary_tx ( |auxiliary_event_tx| {
512+ if let Err ( err) = auxiliary_event_tx. send ( event) {
513+ // The error includes the event
514+ log:: warn!( "LSP is shut down, can't send event:\n {err:?}" ) ;
515+ }
516+ } )
501517}
502518
503519/// Send a message to the LSP client. This is non-blocking and treated on a
@@ -510,7 +526,9 @@ pub(crate) fn log(level: lsp_types::MessageType, message: String) {
510526
511527 // Check that channel is still alive in case the LSP was closed.
512528 // If closed, fallthrough.
513- if let Ok ( _) = auxiliary_tx ( ) . send ( AuxiliaryEvent :: Log ( level, message. clone ( ) ) ) {
529+ if let Ok ( _) = with_auxiliary_tx ( |auxiliary_event_tx| {
530+ auxiliary_event_tx. send ( AuxiliaryEvent :: Log ( level, message. clone ( ) ) )
531+ } ) {
514532 return ;
515533 }
516534
0 commit comments