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
29 changes: 29 additions & 0 deletions java/sdk/src/main/java/com/github/copilot/JsonRpcClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,24 @@ class JsonRpcClient implements AutoCloseable {
private final OutputStream outputStream;
private final Socket socket;
private final Process process;
private final boolean ownsStreams;
private final AtomicLong requestIdCounter = new AtomicLong(0);
private final Map<Long, CompletableFuture<JsonNode>> pendingRequests = new ConcurrentHashMap<>();
private final Map<String, BiConsumer<String, JsonNode>> notificationHandlers = new ConcurrentHashMap<>();
private final ExecutorService readerExecutor;
private volatile boolean running = true;

private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket socket, Process process) {
this(inputStream, outputStream, socket, process, false);
}

private JsonRpcClient(InputStream inputStream, OutputStream outputStream, Socket socket, Process process,
boolean ownsStreams) {
this.inputStream = inputStream;
this.outputStream = outputStream;
this.socket = socket;
this.process = process;
this.ownsStreams = ownsStreams;
this.readerExecutor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "jsonrpc-reader");
t.setDaemon(true);
Expand Down Expand Up @@ -93,6 +100,15 @@ public static JsonRpcClient fromSocket(Socket socket) throws IOException {
return new JsonRpcClient(socket.getInputStream(), socket.getOutputStream(), socket, null);
}

/**
* Creates a JSON-RPC client over arbitrary input/output streams. The client
* takes ownership of the streams and closes them when {@link #close()} is
* called.
*/
public static JsonRpcClient fromStreams(InputStream inputStream, OutputStream outputStream) {
return new JsonRpcClient(inputStream, outputStream, null, null, true);
}

/**
* Registers a handler for JSON-RPC method calls (requests/notifications from
* server).
Expand Down Expand Up @@ -344,6 +360,19 @@ public void close() {
if (process != null) {
process.destroy();
}

if (ownsStreams) {
try {
inputStream.close();
} catch (IOException e) {
LOG.log(Level.FINE, "Error closing input stream", e);
}
try {
outputStream.close();
} catch (IOException e) {
LOG.log(Level.FINE, "Error closing output stream", e);
}
}
}

public boolean isConnected() {
Expand Down
63 changes: 63 additions & 0 deletions java/sdk/src/main/java/com/github/copilot/ffi/FfiOutputStream.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
*--------------------------------------------------------------------------------------------*/

package com.github.copilot.ffi;

import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;

final class FfiOutputStream extends OutputStream {

private final NativeBinding nativeBinding;
private final AtomicInteger connectionId;
private final AtomicBoolean closing;
private final ReentrantLock operationLock;

FfiOutputStream(NativeBinding nativeBinding, AtomicInteger connectionId, AtomicBoolean closing,
ReentrantLock operationLock) {
this.nativeBinding = Objects.requireNonNull(nativeBinding, "nativeBinding must not be null");
this.connectionId = Objects.requireNonNull(connectionId, "connectionId must not be null");
this.closing = Objects.requireNonNull(closing, "closing must not be null");
this.operationLock = Objects.requireNonNull(operationLock, "operationLock must not be null");
}

@Override
public void write(int b) throws IOException {
write(new byte[]{(byte) b}, 0, 1);
}

@Override
public void write(byte[] b, int off, int len) throws IOException {
Objects.requireNonNull(b, "buffer must not be null");
if (off < 0 || len < 0 || off + len > b.length) {
throw new IndexOutOfBoundsException("Invalid off/len for buffer of length " + b.length);
}
if (len == 0) {
return;
}

operationLock.lock();
try {
if (closing.get()) {
throw new IOException("The in-process runtime connection is closed.");
}
int id = connectionId.get();
if (id == 0) {
throw new IOException("The in-process runtime connection is closed.");
}

byte[] payload = (off == 0 && len == b.length) ? b : Arrays.copyOfRange(b, off, off + len);
if (!nativeBinding.connectionWrite(id, payload, payload.length)) {
throw new IOException("Failed to write a frame to the in-process runtime connection.");
}
} finally {
operationLock.unlock();
}
}
}
Loading
Loading