-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Add bidirectional messaging proc-macro-srv #21249
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add bidirectional messaging proc-macro-srv #21249
Conversation
0a41ccb to
bb61e2e
Compare
bb61e2e to
376bca9
Compare
339c074 to
57fdf52
Compare
a01d06d to
19e816d
Compare
ChayimFriedman2
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Overall good code, left some comments.
@Veykril would you like to review this too?
| impl From<(RequestId, Kind, Payload)> for Envelope { | ||
| fn from(value: (RequestId, Kind, Payload)) -> Self { | ||
| Envelope { id: value.0, kind: value.1, payload: value.2 } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| impl From<(RequestId, Kind, Payload)> for Envelope { | |
| fn from(value: (RequestId, Kind, Payload)) -> Self { | |
| Envelope { id: value.0, kind: value.1, payload: value.2 } | |
| } | |
| } |
This is not used (and also not a good practice).
| legacy_protocol::msg::{FlatTree, Message, PanicMessage, ServerConfig}, | ||
| }; | ||
|
|
||
| pub type RequestId = u32; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
4 billion requests are a lot but still in the realm of possibility for this to overflow for a long-lived rust-analyzer process with a lot of macros, so I think better to have this as a u64.
Also, a newtype, maybe?
| pub(crate) enum Protocol { | ||
| LegacyJson { mode: SpanMode }, | ||
| LegacyPostcard { mode: SpanMode }, | ||
| NewPostcard { mode: SpanMode }, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to call this BidirectionalPostcard and ditto for NewJson, so we won't have to rename again when we replace it (and also it's consistent with the module names).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's call it BidirectionalPostcardPrototype (and similar for the cli flags). I expect us to break this a bit here and there so i want us to differentiate that.
In part because i will likely have another look at the proto definitions in general and restructure them
| match self.protocol { | ||
| Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, | ||
| Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, | ||
| Protocol::NewJson { mode } => mode == SpanMode::RustAnalyzer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Veykril will the Jetbrains people want to use the new protocol (but not rust-analyzer span mode)? If not, it makes sense to remove the token id span+new protocols combination completely, unless it's too much work.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't talked to them yet regarding this, but for now I think we should only do the postcard one for simplicity for now.
| tracing.workspace = true | ||
| rustc-hash.workspace = true | ||
| indexmap.workspace = true | ||
| base-db.workspace = true |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally we really do not want to bring in salsa and base-db and friends here, but given that we're going to get rid of this completely after this RP (#t-compiler/rust-analyzer > Non-generic spans), I suppose it's fine?
| } | ||
| } | ||
|
|
||
| pub enum SubRequest { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you need to redefine this enum here, instead of just reuse it?
| }); | ||
|
|
||
| let (subreq_tx, subreq_rx) = unbounded::<proc_macro_srv::SubRequest>(); | ||
| let (subresp_tx, subresp_rx) = unbounded::<proc_macro_srv::SubResponse>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally it's better for channels to be bounded, this way a fast one side a slow other side can flood memory with requests.
| result | ||
| } | ||
|
|
||
| pub fn expand_with_channels<S: ProcMacroSrvSpan>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's some code reuse potential with expand() here, perhaps take a callback.
| Err(bridge::PanicMessage::String(format!("proc-macro `{macro_name}` is missing")).into()) | ||
| } | ||
|
|
||
| pub(crate) fn expand_with_channels<S: ProcMacroSrvSpan>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Make this take Option<Cahnnel>s and make expand() call it, to reuse code.
|
|
||
| - name: Check salsa dependency | ||
| run: "! (cargo tree -p proc-macro-srv-cli | grep -q salsa)" | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Better to comment this only temporarily with a FIXME until we do #t-compiler/rust-analyzer > Non-generic spans.
| cli_to_server: crossbeam_channel::Receiver<SubResponse>, | ||
| server_to_cli: crossbeam_channel::Sender<SubRequest>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would expect us to model this via a callback instead impl FnMut(SubRequest) -> Result<SubResponse>, modelling this with channels seems weird given us sending a request has to be blocking anyways
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that it's structured this way so that it's easier to make it concurrent in the future.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's fine, a single proc-macro execution will still run sequential so a callback works out there. The cli-server should manage the connection of the channels here, the proc-macro server impl should merely be able to say, I want to emit this subrequest, give me the corresponding reply: That's best modeled by a callback.
| match self.protocol { | ||
| Protocol::LegacyJson { mode } => mode == SpanMode::RustAnalyzer, | ||
| Protocol::LegacyPostcard { mode } => mode == SpanMode::RustAnalyzer, | ||
| Protocol::NewJson { mode } => mode == SpanMode::RustAnalyzer, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I haven't talked to them yet regarding this, but for now I think we should only do the postcard one for simplicity for now.
| version: 0, | ||
| protocol: protocol.clone(), | ||
| exited: OnceLock::new(), | ||
| next_request_id: AtomicU32::new(1), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we need this for now? This PR should only allow blocking bidirectionality, no concurrency so we don't run into the risk of having multiple requests outstanding. If we do, havent checked the server-cli loop code yet then we should move that out of this PR and only focus on bidirectionality for now
| struct Callbacks<'de> { | ||
| db: &'de dyn SourceDatabase, | ||
| } | ||
| impl<'db> ClientCallbacks for Callbacks<'db> { | ||
| fn handle_sub_request(&mut self, req: SubRequest) -> Result<SubResponse, ServerError> { | ||
| match req { | ||
| SubRequest::SourceText { file_id, start, end } => { | ||
| let file = FileId::from_raw(file_id); | ||
| let text = self.db.file_text(file).text(self.db); | ||
|
|
||
| let slice = text.get(start as usize..end as usize).map(|s| s.to_owned()); | ||
|
|
||
| Ok(SubResponse::SourceTextResult { text: slice }) | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This to me feels like the wrong place to put this. I think the handler implementations need to happen in the client side crate, that is rust-analyzer. That will also allow us to remove the unwanted dependency on base-db in here. This code shouldn't need to know about databases and such.
This PR adds a bidirectional proc-macro protocol so the server can ask the client for data while expanding a macro . The approach is still synchronous and single-request-at-a-time. The server may emit sub-requests during expansion, the client replies immediately, and the server then finishes with the final response. All messages use the same request id, and stdin/stdout remain strictly ordered. Legacy protocols are untouched