Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docker-compose.build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ services:
dockerfile: server/deploy/Dockerfile
context: .
runtime:
image: ehenoma/jsheets-latest:latest
image: ehenoma/jsheets-runtime:latest
build:
dockerfile: runtime/deploy/Dockerfile
context: .
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public final class ForkedExecutionControl extends JdiExecutionControl {

private final Lock stopLock = new ReentrantLock();
private boolean userCodeRunning = false;
private volatile boolean closed;

private final String remoteAgentClass;
private final ClassFileStore classFileStore;
Expand Down Expand Up @@ -72,6 +73,9 @@ public String invoke(String classname, String methodName)
throws RunException, EngineTerminationException, InternalException
{
String result;
if (isClosed()) {
throw new IllegalStateException("closed");
}
updateUserCodeRunning(true);
try {
result = super.invoke(classname, methodName);
Expand All @@ -96,6 +100,7 @@ public void stop() throws EngineTerminationException, InternalException {
try {
if (userCodeRunning) {
new RemoteInterrupt(vm(), remoteAgentClass).runInSuspendedMode();
closed = true;
}
} finally {
stopLock.unlock();
Expand All @@ -107,6 +112,12 @@ public void stop() throws EngineTerminationException, InternalException {
public void close() {
super.close();
disposeMachine();
stopLock.lock();
closed = true;
}

public boolean isClosed() {
return closed;
}

synchronized void disposeMachine() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

import com.google.common.util.concurrent.ThreadFactoryBuilder;

import jdk.jshell.spi.ExecutionControlProvider;
import jsheets.evaluation.shell.environment.ClassFileStore;
Expand All @@ -14,23 +18,39 @@ public static ForkedExecutionEnvironment create(
) {
Objects.requireNonNull(store, "store");
Objects.requireNonNull(virtualMachineOptions, "virtualMachineOptions");
return new ForkedExecutionEnvironment(store, virtualMachineOptions);
return new ForkedExecutionEnvironment(
store,
virtualMachineOptions,
createDaemonScheduler()
);
}

private static ScheduledExecutorService createDaemonScheduler() {
var factory = new ThreadFactoryBuilder().setDaemon(true).build();
return Executors.newScheduledThreadPool(1, factory);
}

private final ClassFileStore store;
private final Collection<String> virtualMachineOptions;
private final ScheduledExecutorService scheduler;

private ForkedExecutionEnvironment(
ClassFileStore store,
Collection<String> virtualMachineOptions
Collection<String> virtualMachineOptions,
ScheduledExecutorService scheduler
) {
this.store = store;
this.virtualMachineOptions = virtualMachineOptions;
this.scheduler = scheduler;
}

@Override
public ExecutionControlProvider control(String name) {
return ForkingExecutionControlProvider.create(virtualMachineOptions, store);
return ForkingExecutionControlProvider.create(
virtualMachineOptions,
store,
scheduler
);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,21 @@
import java.net.InetAddress;
import java.net.ServerSocket;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.BiFunction;
import java.util.function.Consumer;

import com.google.common.flogger.FluentLogger;
import com.google.common.util.concurrent.ThreadFactoryBuilder;

import com.sun.jdi.VirtualMachine;
import jdk.jshell.execution.JdiInitiator;
import jdk.jshell.execution.RemoteExecutionControl;
Expand All @@ -33,42 +38,57 @@
public final class ForkingExecutionControlProvider
implements ExecutionControlProvider {

private static final Duration defaultTimeout = Duration.ofMillis(3000);
private static final FluentLogger log = FluentLogger.forEnclosingClass();

public static ForkingExecutionControlProvider create() {
return create(List.of(), EmptyClassFileStore.create());
return create(
List.of(),
EmptyClassFileStore.create(),
Executors.newScheduledThreadPool(
1,
new ThreadFactoryBuilder().setDaemon(true).build()
)
);
}

private static final Duration defaultTimeout = Duration.ofMillis(3000);
private static final Duration defaultExecutionTimeout =
Duration.ofSeconds(30);

public static ForkingExecutionControlProvider create(
Collection<String> rawVirtualMachineOptions,
ClassFileStore classFileStore
ClassFileStore classFileStore,
ScheduledExecutorService scheduler
) {
Objects.requireNonNull(classFileStore, "classFileStore");
Objects.requireNonNull(rawVirtualMachineOptions, "rawVirtualMachineOptions");
var host = InetAddress.getLoopbackAddress().getHostName();
return new ForkingExecutionControlProvider(
host,
defaultExecutionTimeout,
defaultTimeout,
List.copyOf(rawVirtualMachineOptions),
classFileStore
classFileStore,
scheduler
);
}

private final String host;
private final Duration timeout;
private final Duration connectTimeout;
private final Duration executionTimeout;
private final List<String> rawVirtualMachineOptions;
private final ScheduledExecutorService scheduler;
private final ClassFileStore classFileStore;

private ForkingExecutionControlProvider(
String host,
Duration timeout,
Duration executionTimeout,
Duration connectTimeout,
List<String> rawVirtualMachineOptions,
ClassFileStore classFileStore
ClassFileStore classFileStore,
ScheduledExecutorService scheduler
) {
this.host = host;
this.timeout = timeout;
this.executionTimeout = executionTimeout;
this.connectTimeout = connectTimeout;
this.rawVirtualMachineOptions = rawVirtualMachineOptions;
this.classFileStore = classFileStore;
this.scheduler = scheduler;
}

@Override
Expand Down Expand Up @@ -101,7 +121,7 @@ private Box initiate(int port) {
/* remoteAgentClassName */ remoteAgentClassName,
/* controlledLaunch */ false,
/* host */ "",
/* timeout */ (int) timeout.toMillis(),
/* timeout */ (int) connectTimeout.toMillis(),
/* connectorOptions*/ Collections.emptyMap()
);
return new Box(
Expand All @@ -115,7 +135,7 @@ private Box initiate(int port) {
ExecutionControl create(ExecutionEnv environment) throws IOException {
var address = InetAddress.getLoopbackAddress();
try (var listener = new ServerSocket(0, backlog, address)) {
listener.setSoTimeout((int) timeout.toMillis());
listener.setSoTimeout((int) connectTimeout.toMillis());
var box = initiate(listener.getLocalPort());
return accept(listener, environment, box);
}
Expand Down Expand Up @@ -164,10 +184,28 @@ private BiFunction<ObjectInput, ObjectOutput, ExecutionControl> createControl(
);
hooks.add(event -> environment.closeDown());
hooks.add(event -> control.disposeMachine());
scheduleExecutionTimeout(control);
return control;
};
}

private void scheduleExecutionTimeout(ForkedExecutionControl control) {
scheduler.schedule(() -> {
if (!control.isClosed()) {
log.atInfo().log("stopping long running remote");
try {
control.stop();
} catch (Exception failedStop) {
log.atWarning()
.withCause(failedStop)
.atMostEvery(5, TimeUnit.SECONDS)
.log("failed to stop long running remote");
}
control.close();
}
}, executionTimeout.toMillis(), TimeUnit.MILLISECONDS);
}

private Map<String, OutputStream> createOutputs(ExecutionEnv environment) {
return Map.of(
"out", environment.userOut(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,20 @@ private SandboxClassFileCheck(Collection<Rule> rules) {

@Override
public void redefine(ExecutionControl.ClassBytecodes[] bytecodes) {
analyze(bytecodes);
}

@Override
public void load(ExecutionControl.ClassBytecodes[] bytecodes) {
analyze(bytecodes);
}

private void analyze(ExecutionControl.ClassBytecodes[] bytecodes) {
var analysis = Analysis.create();
var check = SandboxBytecodeCheck.withRules(rules);
for (var binary : bytecodes) {
check.run(analysis, binary.bytecodes());
}
analysis.reportViolations();
}

@Override
public void load(ExecutionControl.ClassBytecodes[] bytecodes) {

}
}
8 changes: 8 additions & 0 deletions runtime/src/main/java/jsheets/runtime/App.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
package jsheets.runtime;

import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Executors;

import com.google.common.flogger.FluentLogger;

import com.google.inject.Guice;
import com.google.inject.Injector;

import jdk.jshell.JShell;
import jsheets.evaluation.sandbox.access.AccessGraph;
import jsheets.evaluation.sandbox.validation.ForbiddenMemberFilter;
import jsheets.evaluation.shell.environment.fork.ForkingExecutionControlProvider;
import jsheets.evaluation.shell.environment.sandbox.SandboxClassFileCheck;
import jsheets.runtime.evaluation.EvaluationModule;

public final class App {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,5 +63,4 @@ Collection<String> listVirtualMachineOptions(Config config) {
virtualMachineOptionsKey().in(config).or("").trim().split("\n")
);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ java.lang
!java.lang.invoke
java.lang.System
!java.lang.System#exit
!java.lang.Runtime
!java.lang.Process
!java.lang.ProcessBuilder
!java.lang.SecurityManager
!java.lang.ThreadDeath
!java.lang.ThreadGroup
!java.util.concurrent
java.text
java.time
Expand Down
13 changes: 13 additions & 0 deletions website/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,10 @@ import {
import {useShare} from './sheet/useShare'
import ImportedSheet from './sheet/ImportedSheet'
import ShareModal from './sheet/ShareModal'
import {createBlankSheet, createWelcomeSheet} from './sheet/defaultSheet'

const welcomeSheet = createWelcomeSheet()
const blankSheet = createBlankSheet()

export default function App() {
const [evaluate, evaluating] = useEvaluate()
Expand Down Expand Up @@ -41,8 +45,17 @@ export default function App() {
captureSnippet={captureSnippet}
/>
</Route>
<Route path={['/new', '/blank']}>
<Sheet
initial={blankSheet}
evaluating={evaluating}
evaluate={evaluate}
captureSnippet={captureSnippet}
/>
</Route>
<Route path="/">
<Sheet
initial={welcomeSheet}
evaluating={evaluating}
evaluate={evaluate}
captureSnippet={captureSnippet}
Expand Down
6 changes: 6 additions & 0 deletions website/src/header/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import React from "react";
import * as Styled from './Header.style'
import {useTranslation} from "react-i18next";
import ToggleButton from '../theme/ToggleButton'
import {GithubOutlined} from '@ant-design/icons'

export interface HeaderProperties {
onShare?: () => void
Expand All @@ -24,6 +25,11 @@ export default function Header(properties: HeaderProperties) {
onClick={properties.onShare}
>{t('menu.share')}</Button>
<ToggleButton/>
<Button
shape="circle"
icon={<GithubOutlined/>}
onClick={() => window.open('https://github.com/java-sheets/java-sheets', '_blank')}
/>
</Space>
</Styled.Menu>
</Styled.Header>
Expand Down
5 changes: 3 additions & 2 deletions website/src/sheet/Sheet.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import {
import {useDispatch} from 'react-redux'
import {StartEvaluationRequest} from "@jsheets/protocol/src/jsheets/api/snippet_runtime_pb";
import {reorderSnippet} from './state'
import {listSnippetsInState, SnippetState} from './index'
import {listSnippetsInState, SheetState, SnippetState} from './index'
import Snippet, {SnippetReference} from './snippet/Snippet'
import {useDraggableIds} from './snippet/draggableId'

Expand Down Expand Up @@ -47,13 +47,14 @@ function newSnippetTemplate(): Partial<SnippetState> {
export type CaptureSnippetReference = (id: string, snippet: SnippetReference) => void

export interface SheetProperties {
initial?: SheetState
evaluating?: boolean
evaluate: (request: StartEvaluationRequest) => void
captureSnippet?: CaptureSnippetReference
}

export default function Sheet(properties: SheetProperties) {
const {sheet, addSnippet, moveSnippet} = useSheet()
const {sheet, addSnippet, moveSnippet} = useSheet(properties.initial)
const reorder = useReorder()

const snippets = useMemo(() => {
Expand Down
Loading