Bio server

We could use a traditional way to implement a server, let’s call it BioServer.

public class BioServer extends AbstractServer implements Server {
    private ServerSocket serverSocket;
    private final String host;
    private final int port;

    private final CommandReader commandReader;
    private final AtomicBoolean stop;

    private final RequestMeasureCollector requestMeasureCollector;

    /**
     * Init server with given host, port, and process.
     *
     * @param host             host
     * @param port             port
     * @param requestProcessor request processor
     * @param type             database type
     */
    public BioServer(String host, int port, RequestProcessor requestProcessor, DatabaseType type) {
        super(type, requestProcessor);
        this.host = host;
        this.port = port;
        this.stop = new AtomicBoolean(false);
        this.commandReader = new ClientCommandReader();
        this.requestMeasureCollector = new RequestMeasureCollector("BIO");
    }

    /**
     * Start the server.
     * Process client request one by one.
     */
    public void start() {
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(host, port));
            System.out.println("Jimds is ready to accept requests on port " + port);
            int times = 0;
            while (!stop.get()) {
                Socket socket = serverSocket.accept();
                process(socket);

                times++;
                if (times % 100 == 0) {
                    times = 0;
                    requestMeasureCollector.print();
                }
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        } finally {
            closeQuietly(serverSocket);
        }
    }
}

The process method is executed in current thread.

public class BioServer extends AbstractServer implements Server {
    protected void process(Socket socket) {
        BufferedInputStream inputStream = null;
        BufferedOutputStream outputStream = null;
        MeasureData measureData = new MeasureData();
        try {
            measureData.add(RequestMeasureCollector.FIELD_NAME_CONNECTED);
            inputStream = new BufferedInputStream(socket.getInputStream());
            outputStream = new BufferedOutputStream(socket.getOutputStream());
            DataReader dataReader = new StreamDataReader(inputStream, bufSize, MAX_COMMAND_LENGTH);
            List<ByteWord> words = commandReader.read(dataReader);
            measureData.add(RequestMeasureCollector.FIELD_NAME_READ);
            Command command = CommandFactory.parseCommand(words);
            measureData.add(RequestMeasureCollector.FIELD_NAME_PARSE);
            NetworkData data = requestProcessor.process(database, command);
            measureData.add(RequestMeasureCollector.FIELD_NAME_PROCESS);
            data.write(new StreamDataWriter(outputStream));
            outputStream.flush();
            measureData.add(RequestMeasureCollector.FIELD_NAME_SEND);
        } catch (JimdsException e) {
            measureData.add(RequestMeasureCollector.FIELD_NAME_ERROR);
            // internal exception, write to output
            NetworkData data = new NetworkError(e.getMessage());
            try {
                data.write(new StreamDataWriter(outputStream));
            } catch (IOException ex) {
                e.printStackTrace();
            }
        } catch (Throwable e) {
            measureData.add(RequestMeasureCollector.FIELD_NAME_ERROR);
            // should only be thrown when writing data to output
            // if input throws IOException, an error message will be written to output.
            // throw new RuntimeException(e);
            // ignore the message, and close socket
            e.printStackTrace();
        } finally {
            requestMeasureCollector.add(measureData);
            closeQuietly(inputStream);
            closeQuietly(outputStream);
            closeQuietly(socket);
        }
    }
}

In single thread mode, BioServer also has a good performance. Here is the benchmark result for simple string command, small size refers to 1024 bytes, and testStringGetSet uses 10K values.

Benchmark                                                                    Mode     Cnt     Score     Error  Units
BenchmarkTest.testStringGetSet                                              thrpt       5  1804.792 ± 185.597  ops/s
BenchmarkTest.testStringGetSetSmallSize                                     thrpt       5  4830.196 ±  98.344  ops/s
BenchmarkTest.testStringGetSet                                               avgt       5     0.002 ±   0.001   s/op
BenchmarkTest.testStringGetSetSmallSize                                      avgt       5     0.001 ±   0.001   s/op
BenchmarkTest.testStringGetSet                                             sample   73744     0.003 ±   0.001   s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.00                      sample             0.002             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.50                      sample             0.003             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.90                      sample             0.003             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.95                      sample             0.004             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.99                      sample             0.005             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.999                     sample             0.014             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p0.9999                    sample             0.031             s/op
BenchmarkTest.testStringGetSet:testStringGetSet·p1.00                      sample             0.073             s/op
BenchmarkTest.testStringGetSetSmallSize                                    sample  203342     0.001 ±   0.001   s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.00    sample             0.001             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.50    sample             0.001             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.90    sample             0.001             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.95    sample             0.002             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.99    sample             0.002             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.999   sample             0.003             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p0.9999  sample             0.021             s/op
BenchmarkTest.testStringGetSetSmallSize:testStringGetSetSmallSize·p1.00    sample             0.056             s/op
BenchmarkTest.testStringGetSet                                                 ss             0.016             s/op
BenchmarkTest.testStringGetSetSmallSize                                        ss             0.006             s/op