Create RunningKernel trait to allow for native and remote jupyter kernels (#20842)

Starts setting up a `RunningKernel` trait to make the remote kernel
implementation easy to get started with. No release notes until this is
all hooked up.

Release Notes:

- N/A
This commit is contained in:
Kyle Kelley 2024-11-18 18:12:23 -08:00 committed by GitHub
parent 343c88574a
commit bd0f197415
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 1600 additions and 1088 deletions

1985
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -368,12 +368,14 @@ indexmap = { version = "1.6.2", features = ["serde"] }
indoc = "2" indoc = "2"
itertools = "0.13.0" itertools = "0.13.0"
jsonwebtoken = "9.3" jsonwebtoken = "9.3"
jupyter-protocol = { version = "0.2.0" }
jupyter-websocket-client = { version = "0.4.1" }
libc = "0.2" libc = "0.2"
linkify = "0.10.0" linkify = "0.10.0"
log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] } log = { version = "0.4.16", features = ["kv_unstable_serde", "serde"] }
markup5ever_rcdom = "0.3.0" markup5ever_rcdom = "0.3.0"
nanoid = "0.4" nanoid = "0.4"
nbformat = "0.5.0" nbformat = "0.6.0"
nix = "0.29" nix = "0.29"
num-format = "0.4.4" num-format = "0.4.4"
once_cell = "1.19.0" once_cell = "1.19.0"
@ -407,7 +409,7 @@ reqwest = { git = "https://github.com/zed-industries/reqwest.git", rev = "fd110f
"stream", "stream",
] } ] }
rsa = "0.9.6" rsa = "0.9.6"
runtimelib = { version = "0.19.0", default-features = false, features = [ runtimelib = { version = "0.21.0", default-features = false, features = [
"async-dispatcher-runtime", "async-dispatcher-runtime",
] } ] }
rustc-demangle = "0.1.23" rustc-demangle = "0.1.23"

View file

@ -402,7 +402,7 @@ fn session_state(session: View<Session>, cx: &WindowContext) -> ReplMenuState {
status: session.kernel.status(), status: session.kernel.status(),
..fill_fields() ..fill_fields()
}, },
Kernel::RunningKernel(kernel) => match &kernel.execution_state { Kernel::RunningKernel(kernel) => match &kernel.execution_state() {
ExecutionState::Idle => ReplMenuState { ExecutionState::Idle => ReplMenuState {
tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(), tooltip: format!("Run code on {} ({})", kernel_name, kernel_language).into(),
indicator: Some(Indicator::dot().color(Color::Success)), indicator: Some(Indicator::dot().color(Color::Success)),

View file

@ -25,6 +25,8 @@ feature_flags.workspace = true
futures.workspace = true futures.workspace = true
gpui.workspace = true gpui.workspace = true
image.workspace = true image.workspace = true
jupyter-websocket-client.workspace = true
jupyter-protocol.workspace = true
language.workspace = true language.workspace = true
log.workspace = true log.workspace = true
markdown_preview.workspace = true markdown_preview.workspace = true

View file

@ -0,0 +1,227 @@
mod native_kernel;
use std::{fmt::Debug, future::Future, path::PathBuf};
use futures::{
channel::mpsc::{self, Receiver},
future::Shared,
stream,
};
use gpui::{AppContext, Model, Task};
use language::LanguageName;
pub use native_kernel::*;
mod remote_kernels;
use project::{Project, WorktreeId};
pub use remote_kernels::*;
use anyhow::Result;
use runtimelib::{ExecutionState, JupyterKernelspec, JupyterMessage, KernelInfoReply};
use smol::process::Command;
use ui::SharedString;
pub type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum KernelSpecification {
Remote(RemoteKernelSpecification),
Jupyter(LocalKernelSpecification),
PythonEnv(LocalKernelSpecification),
}
impl KernelSpecification {
pub fn name(&self) -> SharedString {
match self {
Self::Jupyter(spec) => spec.name.clone().into(),
Self::PythonEnv(spec) => spec.name.clone().into(),
Self::Remote(spec) => spec.name.clone().into(),
}
}
pub fn type_name(&self) -> SharedString {
match self {
Self::Jupyter(_) => "Jupyter".into(),
Self::PythonEnv(_) => "Python Environment".into(),
Self::Remote(_) => "Remote".into(),
}
}
pub fn path(&self) -> SharedString {
SharedString::from(match self {
Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(),
Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(),
Self::Remote(spec) => spec.url.to_string(),
})
}
pub fn language(&self) -> SharedString {
SharedString::from(match self {
Self::Jupyter(spec) => spec.kernelspec.language.clone(),
Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
Self::Remote(spec) => spec.kernelspec.language.clone(),
})
}
}
pub fn python_env_kernel_specifications(
project: &Model<Project>,
worktree_id: WorktreeId,
cx: &mut AppContext,
) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
let python_language = LanguageName::new("Python");
let toolchains = project
.read(cx)
.available_toolchains(worktree_id, python_language, cx);
let background_executor = cx.background_executor().clone();
async move {
let toolchains = if let Some(toolchains) = toolchains.await {
toolchains
} else {
return Ok(Vec::new());
};
let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| {
background_executor.spawn(async move {
let python_path = toolchain.path.to_string();
// Check if ipykernel is installed
let ipykernel_check = Command::new(&python_path)
.args(&["-c", "import ipykernel"])
.output()
.await;
if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() {
// Create a default kernelspec for this environment
let default_kernelspec = JupyterKernelspec {
argv: vec![
python_path.clone(),
"-m".to_string(),
"ipykernel_launcher".to_string(),
"-f".to_string(),
"{connection_file}".to_string(),
],
display_name: toolchain.name.to_string(),
language: "python".to_string(),
interrupt_mode: None,
metadata: None,
env: None,
};
Some(KernelSpecification::PythonEnv(LocalKernelSpecification {
name: toolchain.name.to_string(),
path: PathBuf::from(&python_path),
kernelspec: default_kernelspec,
}))
} else {
None
}
})
});
let kernel_specs = futures::future::join_all(kernelspecs)
.await
.into_iter()
.flatten()
.collect();
anyhow::Ok(kernel_specs)
}
}
pub trait RunningKernel: Send + Debug {
fn request_tx(&self) -> mpsc::Sender<JupyterMessage>;
fn working_directory(&self) -> &PathBuf;
fn execution_state(&self) -> &ExecutionState;
fn set_execution_state(&mut self, state: ExecutionState);
fn kernel_info(&self) -> Option<&KernelInfoReply>;
fn set_kernel_info(&mut self, info: KernelInfoReply);
fn force_shutdown(&mut self) -> anyhow::Result<()>;
}
#[derive(Debug, Clone)]
pub enum KernelStatus {
Idle,
Busy,
Starting,
Error,
ShuttingDown,
Shutdown,
Restarting,
}
impl KernelStatus {
pub fn is_connected(&self) -> bool {
match self {
KernelStatus::Idle | KernelStatus::Busy => true,
_ => false,
}
}
}
impl ToString for KernelStatus {
fn to_string(&self) -> String {
match self {
KernelStatus::Idle => "Idle".to_string(),
KernelStatus::Busy => "Busy".to_string(),
KernelStatus::Starting => "Starting".to_string(),
KernelStatus::Error => "Error".to_string(),
KernelStatus::ShuttingDown => "Shutting Down".to_string(),
KernelStatus::Shutdown => "Shutdown".to_string(),
KernelStatus::Restarting => "Restarting".to_string(),
}
}
}
#[derive(Debug)]
pub enum Kernel {
RunningKernel(Box<dyn RunningKernel>),
StartingKernel(Shared<Task<()>>),
ErroredLaunch(String),
ShuttingDown,
Shutdown,
Restarting,
}
impl From<&Kernel> for KernelStatus {
fn from(kernel: &Kernel) -> Self {
match kernel {
Kernel::RunningKernel(kernel) => match kernel.execution_state() {
ExecutionState::Idle => KernelStatus::Idle,
ExecutionState::Busy => KernelStatus::Busy,
},
Kernel::StartingKernel(_) => KernelStatus::Starting,
Kernel::ErroredLaunch(_) => KernelStatus::Error,
Kernel::ShuttingDown => KernelStatus::ShuttingDown,
Kernel::Shutdown => KernelStatus::Shutdown,
Kernel::Restarting => KernelStatus::Restarting,
}
}
}
impl Kernel {
pub fn status(&self) -> KernelStatus {
self.into()
}
pub fn set_execution_state(&mut self, status: &ExecutionState) {
if let Kernel::RunningKernel(running_kernel) = self {
running_kernel.set_execution_state(status.clone());
}
}
pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
if let Kernel::RunningKernel(running_kernel) = self {
running_kernel.set_kernel_info(kernel_info.clone());
}
}
pub fn is_shutting_down(&self) -> bool {
match self {
Kernel::Restarting | Kernel::ShuttingDown => true,
Kernel::RunningKernel(_)
| Kernel::StartingKernel(_)
| Kernel::ErroredLaunch(_)
| Kernel::Shutdown => false,
}
}
}

View file

@ -1,69 +1,24 @@
use anyhow::{Context as _, Result}; use anyhow::{Context as _, Result};
use futures::{ use futures::{
channel::mpsc::{self, Receiver}, channel::mpsc::{self},
future::Shared, stream::{SelectAll, StreamExt},
stream::{self, SelectAll, StreamExt},
SinkExt as _, SinkExt as _,
}; };
use gpui::{AppContext, EntityId, Model, Task}; use gpui::{AppContext, EntityId, Task};
use language::LanguageName; use jupyter_protocol::{JupyterMessage, JupyterMessageContent, KernelInfoReply};
use project::{Fs, Project, WorktreeId}; use project::Fs;
use runtimelib::{ use runtimelib::{dirs, ConnectionInfo, ExecutionState, JupyterKernelspec};
dirs, ConnectionInfo, ExecutionState, JupyterKernelspec, JupyterMessage, JupyterMessageContent,
KernelInfoReply,
};
use smol::{net::TcpListener, process::Command}; use smol::{net::TcpListener, process::Command};
use std::{ use std::{
env, env,
fmt::Debug, fmt::Debug,
future::Future,
net::{IpAddr, Ipv4Addr, SocketAddr}, net::{IpAddr, Ipv4Addr, SocketAddr},
path::PathBuf, path::PathBuf,
sync::Arc, sync::Arc,
}; };
use ui::SharedString;
use uuid::Uuid; use uuid::Uuid;
#[derive(Debug, Clone, PartialEq, Eq)] use super::{JupyterMessageChannel, RunningKernel};
pub enum KernelSpecification {
Remote(RemoteKernelSpecification),
Jupyter(LocalKernelSpecification),
PythonEnv(LocalKernelSpecification),
}
impl KernelSpecification {
pub fn name(&self) -> SharedString {
match self {
Self::Jupyter(spec) => spec.name.clone().into(),
Self::PythonEnv(spec) => spec.name.clone().into(),
Self::Remote(spec) => spec.name.clone().into(),
}
}
pub fn type_name(&self) -> SharedString {
match self {
Self::Jupyter(_) => "Jupyter".into(),
Self::PythonEnv(_) => "Python Environment".into(),
Self::Remote(_) => "Remote".into(),
}
}
pub fn path(&self) -> SharedString {
SharedString::from(match self {
Self::Jupyter(spec) => spec.path.to_string_lossy().to_string(),
Self::PythonEnv(spec) => spec.path.to_string_lossy().to_string(),
Self::Remote(spec) => spec.url.to_string(),
})
}
pub fn language(&self) -> SharedString {
SharedString::from(match self {
Self::Jupyter(spec) => spec.kernelspec.language.clone(),
Self::PythonEnv(spec) => spec.kernelspec.language.clone(),
Self::Remote(spec) => spec.kernelspec.language.clone(),
})
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct LocalKernelSpecification { pub struct LocalKernelSpecification {
@ -80,22 +35,6 @@ impl PartialEq for LocalKernelSpecification {
impl Eq for LocalKernelSpecification {} impl Eq for LocalKernelSpecification {}
#[derive(Debug, Clone)]
pub struct RemoteKernelSpecification {
pub name: String,
pub url: String,
pub token: String,
pub kernelspec: JupyterKernelspec,
}
impl PartialEq for RemoteKernelSpecification {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.url == other.url
}
}
impl Eq for RemoteKernelSpecification {}
impl LocalKernelSpecification { impl LocalKernelSpecification {
#[must_use] #[must_use]
fn command(&self, connection_path: &PathBuf) -> Result<Command> { fn command(&self, connection_path: &PathBuf) -> Result<Command> {
@ -147,95 +86,7 @@ async fn peek_ports(ip: IpAddr) -> Result<[u16; 5]> {
Ok(ports) Ok(ports)
} }
#[derive(Debug, Clone)] pub struct NativeRunningKernel {
pub enum KernelStatus {
Idle,
Busy,
Starting,
Error,
ShuttingDown,
Shutdown,
Restarting,
}
impl KernelStatus {
pub fn is_connected(&self) -> bool {
match self {
KernelStatus::Idle | KernelStatus::Busy => true,
_ => false,
}
}
}
impl ToString for KernelStatus {
fn to_string(&self) -> String {
match self {
KernelStatus::Idle => "Idle".to_string(),
KernelStatus::Busy => "Busy".to_string(),
KernelStatus::Starting => "Starting".to_string(),
KernelStatus::Error => "Error".to_string(),
KernelStatus::ShuttingDown => "Shutting Down".to_string(),
KernelStatus::Shutdown => "Shutdown".to_string(),
KernelStatus::Restarting => "Restarting".to_string(),
}
}
}
impl From<&Kernel> for KernelStatus {
fn from(kernel: &Kernel) -> Self {
match kernel {
Kernel::RunningKernel(kernel) => match kernel.execution_state {
ExecutionState::Idle => KernelStatus::Idle,
ExecutionState::Busy => KernelStatus::Busy,
},
Kernel::StartingKernel(_) => KernelStatus::Starting,
Kernel::ErroredLaunch(_) => KernelStatus::Error,
Kernel::ShuttingDown => KernelStatus::ShuttingDown,
Kernel::Shutdown => KernelStatus::Shutdown,
Kernel::Restarting => KernelStatus::Restarting,
}
}
}
#[derive(Debug)]
pub enum Kernel {
RunningKernel(RunningKernel),
StartingKernel(Shared<Task<()>>),
ErroredLaunch(String),
ShuttingDown,
Shutdown,
Restarting,
}
impl Kernel {
pub fn status(&self) -> KernelStatus {
self.into()
}
pub fn set_execution_state(&mut self, status: &ExecutionState) {
if let Kernel::RunningKernel(running_kernel) = self {
running_kernel.execution_state = status.clone();
}
}
pub fn set_kernel_info(&mut self, kernel_info: &KernelInfoReply) {
if let Kernel::RunningKernel(running_kernel) = self {
running_kernel.kernel_info = Some(kernel_info.clone());
}
}
pub fn is_shutting_down(&self) -> bool {
match self {
Kernel::Restarting | Kernel::ShuttingDown => true,
Kernel::RunningKernel(_)
| Kernel::StartingKernel(_)
| Kernel::ErroredLaunch(_)
| Kernel::Shutdown => false,
}
}
}
pub struct RunningKernel {
pub process: smol::process::Child, pub process: smol::process::Child,
_shell_task: Task<Result<()>>, _shell_task: Task<Result<()>>,
_iopub_task: Task<Result<()>>, _iopub_task: Task<Result<()>>,
@ -248,9 +99,7 @@ pub struct RunningKernel {
pub kernel_info: Option<KernelInfoReply>, pub kernel_info: Option<KernelInfoReply>,
} }
type JupyterMessageChannel = stream::SelectAll<Receiver<JupyterMessage>>; impl Debug for NativeRunningKernel {
impl Debug for RunningKernel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RunningKernel") f.debug_struct("RunningKernel")
.field("process", &self.process) .field("process", &self.process)
@ -258,25 +107,14 @@ impl Debug for RunningKernel {
} }
} }
impl RunningKernel { impl NativeRunningKernel {
pub fn new( pub fn new(
kernel_specification: KernelSpecification, kernel_specification: LocalKernelSpecification,
entity_id: EntityId, entity_id: EntityId,
working_directory: PathBuf, working_directory: PathBuf,
fs: Arc<dyn Fs>, fs: Arc<dyn Fs>,
cx: &mut AppContext, cx: &mut AppContext,
) -> Task<Result<(Self, JupyterMessageChannel)>> { ) -> Task<Result<(Self, JupyterMessageChannel)>> {
let kernel_specification = match kernel_specification {
KernelSpecification::Jupyter(spec) => spec,
KernelSpecification::PythonEnv(spec) => spec,
KernelSpecification::Remote(_spec) => {
// todo!(): Implement remote kernel specification
return Task::ready(Err(anyhow::anyhow!(
"Running remote kernels is not supported"
)));
}
};
cx.spawn(|cx| async move { cx.spawn(|cx| async move {
let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)); let ip = IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1));
let ports = peek_ports(ip).await?; let ports = peek_ports(ip).await?;
@ -315,15 +153,13 @@ impl RunningKernel {
let session_id = Uuid::new_v4().to_string(); let session_id = Uuid::new_v4().to_string();
let mut iopub_socket = connection_info let mut iopub_socket =
.create_client_iopub_connection("", &session_id) runtimelib::create_client_iopub_connection(&connection_info, "", &session_id)
.await?;
let mut shell_socket = connection_info
.create_client_shell_connection(&session_id)
.await?;
let mut control_socket = connection_info
.create_client_control_connection(&session_id)
.await?; .await?;
let mut shell_socket =
runtimelib::create_client_shell_connection(&connection_info, &session_id).await?;
let mut control_socket =
runtimelib::create_client_control_connection(&connection_info, &session_id).await?;
let (mut iopub, iosub) = futures::channel::mpsc::channel(100); let (mut iopub, iosub) = futures::channel::mpsc::channel(100);
@ -410,7 +246,43 @@ impl RunningKernel {
} }
} }
impl Drop for RunningKernel { impl RunningKernel for NativeRunningKernel {
fn request_tx(&self) -> mpsc::Sender<JupyterMessage> {
self.request_tx.clone()
}
fn working_directory(&self) -> &PathBuf {
&self.working_directory
}
fn execution_state(&self) -> &ExecutionState {
&self.execution_state
}
fn set_execution_state(&mut self, state: ExecutionState) {
self.execution_state = state;
}
fn kernel_info(&self) -> Option<&KernelInfoReply> {
self.kernel_info.as_ref()
}
fn set_kernel_info(&mut self, info: KernelInfoReply) {
self.kernel_info = Some(info);
}
fn force_shutdown(&mut self) -> anyhow::Result<()> {
match self.process.kill() {
Ok(_) => Ok(()),
Err(error) => Err(anyhow::anyhow!(
"Failed to kill the kernel process: {}",
error
)),
}
}
}
impl Drop for NativeRunningKernel {
fn drop(&mut self) { fn drop(&mut self) {
std::fs::remove_file(&self.connection_path).ok(); std::fs::remove_file(&self.connection_path).ok();
self.request_tx.close_channel(); self.request_tx.close_channel();
@ -467,72 +339,6 @@ async fn read_kernels_dir(path: PathBuf, fs: &dyn Fs) -> Result<Vec<LocalKernelS
Ok(valid_kernelspecs) Ok(valid_kernelspecs)
} }
pub fn python_env_kernel_specifications(
project: &Model<Project>,
worktree_id: WorktreeId,
cx: &mut AppContext,
) -> impl Future<Output = Result<Vec<KernelSpecification>>> {
let python_language = LanguageName::new("Python");
let toolchains = project
.read(cx)
.available_toolchains(worktree_id, python_language, cx);
let background_executor = cx.background_executor().clone();
async move {
let toolchains = if let Some(toolchains) = toolchains.await {
toolchains
} else {
return Ok(Vec::new());
};
let kernelspecs = toolchains.toolchains.into_iter().map(|toolchain| {
background_executor.spawn(async move {
let python_path = toolchain.path.to_string();
// Check if ipykernel is installed
let ipykernel_check = Command::new(&python_path)
.args(&["-c", "import ipykernel"])
.output()
.await;
if ipykernel_check.is_ok() && ipykernel_check.unwrap().status.success() {
// Create a default kernelspec for this environment
let default_kernelspec = JupyterKernelspec {
argv: vec![
python_path.clone(),
"-m".to_string(),
"ipykernel_launcher".to_string(),
"-f".to_string(),
"{connection_file}".to_string(),
],
display_name: toolchain.name.to_string(),
language: "python".to_string(),
interrupt_mode: None,
metadata: None,
env: None,
};
Some(KernelSpecification::PythonEnv(LocalKernelSpecification {
name: toolchain.name.to_string(),
path: PathBuf::from(&python_path),
kernelspec: default_kernelspec,
}))
} else {
None
}
})
});
let kernel_specs = futures::future::join_all(kernelspecs)
.await
.into_iter()
.flatten()
.collect();
anyhow::Ok(kernel_specs)
}
}
pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKernelSpecification>> { pub async fn local_kernel_specifications(fs: Arc<dyn Fs>) -> Result<Vec<LocalKernelSpecification>> {
let mut data_dirs = dirs::data_dirs(); let mut data_dirs = dirs::data_dirs();

View file

@ -0,0 +1,122 @@
use futures::{channel::mpsc, StreamExt as _};
use gpui::AppContext;
use jupyter_protocol::{ExecutionState, JupyterMessage, KernelInfoReply};
// todo(kyle): figure out if this needs to be different
use runtimelib::JupyterKernelspec;
use super::RunningKernel;
use jupyter_websocket_client::RemoteServer;
use std::fmt::Debug;
#[derive(Debug, Clone)]
pub struct RemoteKernelSpecification {
pub name: String,
pub url: String,
pub token: String,
pub kernelspec: JupyterKernelspec,
}
impl PartialEq for RemoteKernelSpecification {
fn eq(&self, other: &Self) -> bool {
self.name == other.name && self.url == other.url
}
}
impl Eq for RemoteKernelSpecification {}
pub struct RemoteRunningKernel {
remote_server: RemoteServer,
pub working_directory: std::path::PathBuf,
pub request_tx: mpsc::Sender<JupyterMessage>,
pub execution_state: ExecutionState,
pub kernel_info: Option<KernelInfoReply>,
}
impl RemoteRunningKernel {
pub async fn new(
kernelspec: RemoteKernelSpecification,
working_directory: std::path::PathBuf,
request_tx: mpsc::Sender<JupyterMessage>,
_cx: &mut AppContext,
) -> anyhow::Result<(
Self,
(), // Stream<Item=JupyterMessage>
)> {
let remote_server = RemoteServer {
base_url: kernelspec.url,
token: kernelspec.token,
};
// todo: launch a kernel to get a kernel ID
let kernel_id = "not-implemented";
let kernel_socket = remote_server.connect_to_kernel(kernel_id).await?;
let (mut _w, mut _r) = kernel_socket.split();
let (_messages_tx, _messages_rx) = mpsc::channel::<JupyterMessage>(100);
// let routing_task = cx.background_executor().spawn({
// async move {
// while let Some(message) = request_rx.next().await {
// w.send(message).await;
// }
// }
// });
// let messages_rx = r.into();
anyhow::Ok((
Self {
remote_server,
working_directory,
request_tx,
execution_state: ExecutionState::Idle,
kernel_info: None,
},
(),
))
}
}
impl Debug for RemoteRunningKernel {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("RemoteRunningKernel")
// custom debug that keeps tokens out of logs
.field("remote_server url", &self.remote_server.base_url)
.field("working_directory", &self.working_directory)
.field("request_tx", &self.request_tx)
.field("execution_state", &self.execution_state)
.field("kernel_info", &self.kernel_info)
.finish()
}
}
impl RunningKernel for RemoteRunningKernel {
fn request_tx(&self) -> futures::channel::mpsc::Sender<runtimelib::JupyterMessage> {
self.request_tx.clone()
}
fn working_directory(&self) -> &std::path::PathBuf {
&self.working_directory
}
fn execution_state(&self) -> &runtimelib::ExecutionState {
&self.execution_state
}
fn set_execution_state(&mut self, state: runtimelib::ExecutionState) {
self.execution_state = state;
}
fn kernel_info(&self) -> Option<&runtimelib::KernelInfoReply> {
self.kernel_info.as_ref()
}
fn set_kernel_info(&mut self, info: runtimelib::KernelInfoReply) {
self.kernel_info = Some(info);
}
fn force_shutdown(&mut self) -> anyhow::Result<()> {
unimplemented!("force_shutdown")
}
}

View file

@ -1,6 +1,6 @@
pub mod components; pub mod components;
mod jupyter_settings; mod jupyter_settings;
mod kernels; pub mod kernels;
pub mod notebook; pub mod notebook;
mod outputs; mod outputs;
mod repl_editor; mod repl_editor;

View file

@ -1,7 +1,7 @@
use crate::components::KernelListItem; use crate::components::KernelListItem;
use crate::setup_editor_session_actions; use crate::setup_editor_session_actions;
use crate::{ use crate::{
kernels::{Kernel, KernelSpecification, RunningKernel}, kernels::{Kernel, KernelSpecification, NativeRunningKernel},
outputs::{ExecutionStatus, ExecutionView}, outputs::{ExecutionStatus, ExecutionView},
KernelStatus, KernelStatus,
}; };
@ -246,13 +246,19 @@ impl Session {
cx.entity_id().to_string(), cx.entity_id().to_string(),
); );
let kernel = RunningKernel::new( let kernel = match self.kernel_specification.clone() {
self.kernel_specification.clone(), KernelSpecification::Jupyter(kernel_specification)
| KernelSpecification::PythonEnv(kernel_specification) => NativeRunningKernel::new(
kernel_specification,
entity_id, entity_id,
working_directory, working_directory,
self.fs.clone(), self.fs.clone(),
cx, cx,
); ),
KernelSpecification::Remote(_remote_kernel_specification) => {
unimplemented!()
}
};
let pending_kernel = cx let pending_kernel = cx
.spawn(|this, mut cx| async move { .spawn(|this, mut cx| async move {
@ -291,7 +297,7 @@ impl Session {
.detach(); .detach();
let status = kernel.process.status(); let status = kernel.process.status();
session.kernel(Kernel::RunningKernel(kernel), cx); session.kernel(Kernel::RunningKernel(Box::new(kernel)), cx);
let process_status_task = cx.spawn(|session, mut cx| async move { let process_status_task = cx.spawn(|session, mut cx| async move {
let error_message = match status.await { let error_message = match status.await {
@ -416,7 +422,7 @@ impl Session {
fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> { fn send(&mut self, message: JupyterMessage, _cx: &mut ViewContext<Self>) -> anyhow::Result<()> {
if let Kernel::RunningKernel(kernel) = &mut self.kernel { if let Kernel::RunningKernel(kernel) = &mut self.kernel {
kernel.request_tx.try_send(message).ok(); kernel.request_tx().try_send(message).ok();
} }
anyhow::Ok(()) anyhow::Ok(())
@ -631,7 +637,7 @@ impl Session {
match kernel { match kernel {
Kernel::RunningKernel(mut kernel) => { Kernel::RunningKernel(mut kernel) => {
let mut request_tx = kernel.request_tx.clone(); let mut request_tx = kernel.request_tx().clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
let message: JupyterMessage = ShutdownRequest { restart: false }.into(); let message: JupyterMessage = ShutdownRequest { restart: false }.into();
@ -646,7 +652,7 @@ impl Session {
}) })
.ok(); .ok();
kernel.process.kill().ok(); kernel.force_shutdown().ok();
this.update(&mut cx, |session, cx| { this.update(&mut cx, |session, cx| {
session.clear_outputs(cx); session.clear_outputs(cx);
@ -674,7 +680,7 @@ impl Session {
// Do nothing if already restarting // Do nothing if already restarting
} }
Kernel::RunningKernel(mut kernel) => { Kernel::RunningKernel(mut kernel) => {
let mut request_tx = kernel.request_tx.clone(); let mut request_tx = kernel.request_tx().clone();
cx.spawn(|this, mut cx| async move { cx.spawn(|this, mut cx| async move {
// Send shutdown request with restart flag // Send shutdown request with restart flag
@ -692,7 +698,7 @@ impl Session {
cx.background_executor().timer(Duration::from_secs(1)).await; cx.background_executor().timer(Duration::from_secs(1)).await;
// Force kill the kernel if it hasn't shut down // Force kill the kernel if it hasn't shut down
kernel.process.kill().ok(); kernel.force_shutdown().ok();
// Start a new kernel // Start a new kernel
this.update(&mut cx, |session, cx| { this.update(&mut cx, |session, cx| {
@ -727,7 +733,7 @@ impl Render for Session {
let (status_text, interrupt_button) = match &self.kernel { let (status_text, interrupt_button) = match &self.kernel {
Kernel::RunningKernel(kernel) => ( Kernel::RunningKernel(kernel) => (
kernel kernel
.kernel_info .kernel_info()
.as_ref() .as_ref()
.map(|info| info.language_info.name.clone()), .map(|info| info.language_info.name.clone()),
Some( Some(
@ -747,7 +753,7 @@ impl Render for Session {
KernelListItem::new(self.kernel_specification.clone()) KernelListItem::new(self.kernel_specification.clone())
.status_color(match &self.kernel { .status_color(match &self.kernel {
Kernel::RunningKernel(kernel) => match kernel.execution_state { Kernel::RunningKernel(kernel) => match kernel.execution_state() {
ExecutionState::Idle => Color::Success, ExecutionState::Idle => Color::Success,
ExecutionState::Busy => Color::Modified, ExecutionState::Busy => Color::Modified,
}, },