Open Max-ChenFei opened 1 year ago
By checking jupyter's lastest kernels https://github.com/jupyter/jupyter/wiki/Jupyter-kernels, we find two more options for JAVA kernel:
Bacatá : Example Ganymede : Examples IJava: Binder online demo
While Ganymede requires Java 11+, Fiji is bundled with Java version 8, but it works the same with Java 11:
Here is the reference link:
https://forum.image.sc/t/run-fiji-or-imagej-headless-mode-with-java-11/73989
Tutorial of Running Java in a Jupyter Notebook based on IJava or Ganymede.
IJava is also based on JShell
Ganymede based on JShell is most promising at first glance
ganymeda is better
We first learn the theory of kernel:
1. What is kernel in jupyter?
- Kernels:
- Kernels are programming language specific processes that run independently
and interact with the Jupyter Applications and their user interfaces.
- the default kernel called ipykernel,is the reference Jupyter kernel built on top of IPython.
which provides powerful environment for interactive computing in Python
- In addition to Python, other programming language can also be used in the notebook
based on different language kernels
- jupyter-client
-jupyter-client contains the authoritative description of the Jupyter messaging protocol
which is used by clients to communicate with the kernels
- here we have more details of how kernel works in Jupyter:
https://jupyter-client.readthedocs.io/en/stable/messaging.html
- by checking this we could understand how kernels work in Jupyter
- Xeus
- Xeus is a framework meant to facilitate the implemtation of kernels for Jupyter
- since I don't need to implement a kernel, I will pass this part
1.1 Architecture of Jupyter projects:
https://docs.jupyter.org/en/latest/projects/architecture/content-architecture.html
1.1.1 a high-level overview of jupyter projects:
-we also have our simple version as :
-
1.1.2 IPython Kernels
-Terminal Python:
the model REPL(Read-Eval-Print-Loop) of IPython works as:
REPL: -Stands for “read-eval-print-loop”.
An REPL is a program that reads user commands and inputs, evaluates them, and prints results for the user to view.
These steps runs in a perpetual(continuous) loop,
-The IPython Kernel:
All the other interfaces such as the Notebook, the Qt console, ipython console in the terminal, and third-party interfaces -- uses the IPython Kernel.
IPykernel is a separate process which is responsible for running user code
Frontends such as notebook or qtConsole, communicate with iPython Kernel
using JSON messages over ZeroMQ sockets(asynchronous I/O model) https://zeromq.org/
![image] (https://docs.jupyter.org/en/latest/_images/ipy_kernel_and_terminal.png)
https://jupyter-client.readthedocs.io/en/stable/messaging.html
1.2 So let's check jupyter-client focusing on how kernels work and deal with messaging:
A single kernel can be simultaneously(at the same time) connected to one or more frontend
to understand the communication between Front-end and kernels, we need to know basic socket type ROUTER,DEALER,PUB,SUB in ZeroMQ
1.4 Socket API in ZeroMQ
@xp1632 Kernel Example using the command line https://github.com/gap-packages/JupyterKernel
This is a solution when we need to call the function of a close-sourced software
ROUTER
socket allows multiple incomming connection from frontendThis socket is the broadcast channel
where the kernel publishes all side effects
It also publishes the requests from any client that we get through shell
socket
It also publishes its own requests on the stdin
socket
Additionally, in a multi-client scenario, this socket 'broadcast' the side effects and information
taking place with one client, to all other clients over shell
channel.
this ROUTER
socket is connected to all frontends,
and for active frontend(when raw_input) is called
it allows the kernel to request input from the active frontend
DEALER
socket
DEALER
socket.Kernel request
Information tag
All messages are tagged
Shell
The message header
contains information about the message:
{
'msg_id' : str, # typically UUID, must be unique per message
'session' : str, # typically UUID, should be unique per session
'username' : str,
# ISO 8601 timestamp for when the message is created
'date': str,
# All recognized message type strings are listed below.
'msg_type' : str,
# the message protocol version
'version' : '5.0',
}
When a message is the result
of another message
parent_header
is a copy of the header
of the msg that cause
the current message_reply
messages MUST have a parent_header
side-effects typically have a parent, if not, an empty dict should be used
This parent header is used by clients to route message handling to correct place
{
# parent_header is a copy of the request's header
'msg_id': '...',
...
}
Metadata
dict contains information about the message that is not part of content-architecturecontent
dict is the body of the messagemsg_type
in the headerapply
{
"header" : {
"msg_id": "...",
"msg_type": "...",
...
},
"parent_header": {},
"metadata": {},
"content": {},
"buffers": [],
}
The plan for tommorw is to finish the rest part of kernel communication: https://jupyter-client.readthedocs.io/en/stable/messaging.html#the-wire-protocol
Understand
The plan for tommorw is to finish the rest part of kernel communication: https://jupyter-client.readthedocs.io/en/stable/messaging.html#the-wire-protocol
Understand
- Wire Protocol
- Python API
- Msg on shell
- Msg on Control
- Msg on IOPub
- Msg on stdin
- Custom Messages for extension
Then after having primitive understanding how kernel works and communicates,
- we start to install Ganymede in jupyterlab
- If more Java, JShell knowledge needed during installing, we 'll check that
After installing Ganymeda, try official examples first. If questions, you can create issues in that repo
1.7 We continue with The Wire Protocol
{
"header" : {
"msg_id": "...",
"msg_type": "...",
...
},
"parent_header": {},
"metadata": {},
"content": {},
"buffers": [],
}
But this is only a logical representation of the contents
the actual implmentation in ZeroMQ at wire level is different
Every message is serialized to a sequence of at leat six blob of bytes:
[
b'u-u-i-d', # zmq identity(ies)
b'<IDS|MSG>', # delimiter
b'baddad42', # HMAC signature
b'{header}', # serialized header dict
b'{parent_header}', # serialized parent header dict
b'{metadata}', # serialized metadata dict
b'{content}', # serialized content dict
b'\xf0\x9f\x90\xb1' # extra raw data buffer(s)
...
]
execute_result
After the signatureis the actual message, in four frames of bytes
The four dictionaries that compose a messgae are serialized separately
header
parent header
metadata
content
The default and most common serialization is JSON
We can do the serialization by any functions that turns a dict into bytes
{
'header' : dict,
# The msg's unique identifier and type are always stored in the header,
# but the Python implementation copies them to the top level.
'msg_id' : str,
'msg_type' : str,
'parent_header' : dict,
'content' : dict,
'metadata' : dict,
'buffers': list,
}
Request-Reply Pattern
execute_request
via its DEALER
socketstatus:busy
on IOPubexecute_reply
status: idle
Status
All replt messages have a 'status' field, which will have one of following values:
status :ok
means the request was processed successfully,
the remaining content of reply is specified below
status: error
means the request failed due to an error`
the usual content of a successfully reply should be omitted
this is presented when status: error
{
'status' : 'error',
'ename' : str, # Exception name, as a string
'evalue' : str, # Exception value, as a string
'traceback' : list(str), # traceback frames as strings
}
Execute request type
This message type is used by frontends to ask the kernel to execute code.
Here's an example of execute_request
content = {
'code' : str,
# Source code to be executed by the kernel, one single string or more lines.
...
'user_expressions':dict.
# A dict mapping names to expressions to be evaluated in the user's dict
# this information could be retrieved to distinguish between:
# inner prompts, client prompts, different frontend prompts interacting with same kernel
'allow_stdin': True
# If true, code running in the kernel could prompt user for input
# with input_request message
...
}
Execute reply type
content ={
'status' : str,
# One of :'ok' OR 'error' OR 'aborted'
'execution_count': int,
# A global kernel counter that increased by one with each request
# Typically used by clients to display prompt numbers to the user
}
{
'payload' : list(dict),
# Payloads are a way to trigger frontend actions from the kernel.
'user_expressions':dict
}
Payloads
Payloads are a way to trigger frontend actions from the kernel.
page : display data in a pager
set_next_input : create a new output
%load
edit_magic: open a file for editing
%edit
ask_exit: instruct the frontend to prompt the user for exit
-allows the kernel to request exit via %exit
in IPython
-only for console frontends
Other Message Type in shell channel:
Introspection:
inspect_request
, inspect_reply
Completion:
complete_request
, complete_reply
History:
history_request
, history_reply
Comm Info
comm_info_request
target_name
is specified, the reply will only contain target informationKernel Info
kernel_info_request
Code Completeness
When the user enters a line in a console,
There are four possible replies:
complete
code is ready to be executedincomplete
code should prompt for another lineinvalid
code will be sent foe execution so user could see error soonestunknown
if kernel cound not determine, the code may defaultly be sent to executionKernel shut
Kernel Interrupt
Debug request
dummyCell
debugInfo
inspectVariables
copyToGlobals
Streams(stdout,stderr)
Display Data
content = {
# The data dict contains key/value pairs, where the keys are MIME
# types and the values are the raw data of the representation in that
# format.
'data' : dict,
# Any metadata that describes the data
# In IPython, only width and height of images
'metadata' : dict,
# Information not to be persisted to a notebook or other documents.
# Intended to live only during a live kernel session.
# We could store transient key display_id for future update
'transient': dict,
}
Update Display Data
display_id
is specified for a display in 'transient', Code inputs
code
part of execuete_requestExecution results
execute_result
content = {
# The counter for this execution is also provided so that clients can
# display it, since IPython automatically creates variables called _N
# (for prompt N).
'execution_count' : int,
# data and metadata are identical to a display_data message.
# the object being displayed is that passed to the display hook,
# i.e. the *result* of the execution.
'data' : dict,
'metadata' : dict,
}
Execution errors
Kernel status
content = {
# When the kernel starts to handle a message, it will enter the 'busy'
# state and when it finishes, it will enter the 'idle' state.
# The kernel will publish state 'starting' exactly once at process startup.
execution_state : ('busy', 'idle', 'starting')
}
Clear output: is used to clear visible output on the frontend
Debug event: is used by debugging kernels to send debugging events to frontend
In stdin channel, the request/reply pattern goes in opposite direction of most kernel comminication
With stdin socketm Kernel makes the request, the single frontend provides response
This allows code to prompt user for a line of input
A messaging system for developers to add their own objects with Frontend and Kernel
-allow them to communicate with each other with notion of Comm
Comm
exists on both Kernel side and Frontend side, and can comminicate in either direction
Opening a Comm
#Open Comm produce this 'comm_open' msg, to be sent to the other side
{
'comm_id' : 'u-u-i-d',
'target_name' : 'my_comm',
'data' : {}
}
- Every Comm has an ID and target name.
- After `comm_open` messgae has been sent,
there should be corresponding Comm instance on both sides
#comm_msg
{
'comm_id' : 'u-u-i-d',
'data' : {}
}
Comm messages are one-way communication to update comm State
Tearing Down Comms
since comms live on both side
when one side destroys a comm
the other side should be notified by comm_close
message
The message communication protocol between the frontend and kernel, jupyter client, is finished.
To develop kernels for other languages, there are three ways:
Wrapper kernel: reuse communication of IPykernel, implement only execution
Native Kernel: implement both execution and communication in target language
Xeus based kernel: wrapper with better protocol for c++
ref: https://docs.jupyter.org/en/latest/projects/kernels.html