blob: 3619eb1e8cd864c3462d6988822bcccc2fc3f9d1 [file] [log] [blame]
// Copyright 2018 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.starlarkdebug.server;
import com.google.devtools.build.lib.events.Event;
import com.google.devtools.build.lib.events.EventHandler;
import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos.DebugEvent;
import com.google.devtools.build.lib.starlarkdebugging.StarlarkDebuggingProtos.DebugRequest;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import javax.annotation.Nullable;
/**
* Manages the connection to and communication to/from the debugger client. Reading and writing are
* internally synchronized by {@link DebugServerTransport}.
*/
final class DebugServerTransport {
/** Sets up the server transport and blocks while waiting for an incoming connection. */
static DebugServerTransport createAndWaitForClient(
EventHandler eventHandler, ServerSocket serverSocket, boolean verboseLogging)
throws IOException {
// TODO(bazel-team): reject all connections after the first
eventHandler.handle(Event.progress("Waiting for debugger..."));
Socket clientSocket = serverSocket.accept();
eventHandler.handle(Event.info("Debugger connection successfully established."));
return new DebugServerTransport(
eventHandler,
serverSocket,
clientSocket,
clientSocket.getInputStream(),
clientSocket.getOutputStream(),
verboseLogging);
}
private final EventHandler eventHandler;
private final ServerSocket serverSocket;
private final Socket clientSocket;
private final InputStream requestStream;
private final OutputStream eventStream;
private final boolean verboseLogging;
private DebugServerTransport(
EventHandler eventHandler,
ServerSocket serverSocket,
Socket clientSocket,
InputStream requestStream,
OutputStream eventStream,
boolean verboseLogging) {
this.eventHandler = eventHandler;
this.serverSocket = serverSocket;
this.clientSocket = clientSocket;
this.requestStream = requestStream;
this.eventStream = eventStream;
this.verboseLogging = verboseLogging;
}
/**
* Blocks waiting for a properly-formed client request. Returns null if the client connection is
* closed.
*/
@Nullable
DebugRequest readClientRequest() {
synchronized (requestStream) {
try {
DebugRequest request = DebugRequest.parseDelimitedFrom(requestStream);
if (verboseLogging) {
eventHandler.handle(Event.debug("Received debug client request:\n" + request));
}
return request;
} catch (IOException e) {
handleParsingError(e);
return null;
}
}
}
private void handleParsingError(IOException e) {
if (isClosed()) {
// an IOException is expected when the client disconnects -- no need to log an error
return;
}
String message = "Error parsing debug request: " + e.getMessage();
postEvent(DebugEventHelper.error(message));
eventHandler.handle(Event.error(message));
}
/** Posts a debug event. */
void postEvent(DebugEvent event) {
if (verboseLogging) {
eventHandler.handle(Event.debug("Sending debug event:\n" + event));
}
synchronized (eventStream) {
try {
event.writeDelimitedTo(eventStream);
} catch (IOException e) {
eventHandler.handle(Event.error("Failed to send debug event to client: " + e.getMessage()));
}
}
}
void close() throws IOException {
clientSocket.close();
serverSocket.close();
}
boolean isClosed() {
return serverSocket.isClosed() || clientSocket.isClosed();
}
}