All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.smallmind.memcached.cubby.connection.NIOCubbyConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (c) 2007 through 2024 David Berkman
 *
 * This file is part of the SmallMind Code Project.
 *
 * The SmallMind Code Project is free software, you can redistribute
 * it and/or modify it under either, at your discretion...
 *
 * 1) The terms of GNU Affero General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at
 * your option) any later version.
 *
 * ...or...
 *
 * 2) The terms of the Apache License, Version 2.0.
 *
 * The SmallMind Code Project is distributed in the hope that it will
 * be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License or Apache License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * and the Apache License along with the SmallMind Code Project. If not, see
 *  or .
 *
 * Additional permission under the GNU Affero GPL version 3 section 7
 * ------------------------------------------------------------------
 * If you modify this Program, or any covered work, by linking or
 * combining it with other code, such other code is not for that reason
 * alone subject to any of the requirements of the GNU Affero GPL
 * version 3.
 */
package org.smallmind.memcached.cubby.connection;

import java.io.IOException;
import java.net.StandardSocketOptions;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import org.smallmind.memcached.cubby.Authentication;
import org.smallmind.memcached.cubby.ConnectionCoordinator;
import org.smallmind.memcached.cubby.ConnectionTimeoutException;
import org.smallmind.memcached.cubby.CubbyConfiguration;
import org.smallmind.memcached.cubby.CubbyOperationException;
import org.smallmind.memcached.cubby.IncomprehensibleRequestException;
import org.smallmind.memcached.cubby.InvalidSelectionKeyException;
import org.smallmind.memcached.cubby.MemcachedHost;
import org.smallmind.memcached.cubby.command.AuthenticationCommand;
import org.smallmind.memcached.cubby.command.Command;
import org.smallmind.memcached.cubby.command.NoopCommand;
import org.smallmind.memcached.cubby.response.Response;
import org.smallmind.memcached.cubby.translator.KeyTranslator;
import org.smallmind.scribe.pen.LoggerManager;

public class NIOCubbyConnection implements CubbyConnection {

  private final CountDownLatch terminationLatch = new CountDownLatch(1);
  private final AtomicBoolean finished = new AtomicBoolean(false);
  private final AtomicBoolean disconnected = new AtomicBoolean(false);
  private final ConnectionCoordinator connectionCoordinator;
  private final MemcachedHost memcachedHost;
  private final KeyTranslator keyTranslator;
  private final LinkedBlockingQueue requestQueue = new LinkedBlockingQueue<>();
  private final LinkedBlockingQueue responseQueue = new LinkedBlockingQueue<>();
  private final AtomicLong commandCounter = new AtomicLong(0);
  private final Authentication authentication;
  private final long connectionTimeoutMilliseconds;
  private final long keepAliveSeconds;
  private final long defaultRequestTimeoutMilliseconds;
  private SocketChannel socketChannel;
  private Selector selector;
  private SelectionKey selectionKey;
  private RequestWriter requestWriter;
  private ResponseReader responseReader;

  public NIOCubbyConnection (ConnectionCoordinator connectionCoordinator, CubbyConfiguration configuration, MemcachedHost memcachedHost) {

    this.connectionCoordinator = connectionCoordinator;
    this.memcachedHost = memcachedHost;

    keyTranslator = configuration.getKeyTranslator();
    authentication = configuration.getAuthentication();
    connectionTimeoutMilliseconds = configuration.getConnectionTimeoutMilliseconds();
    keepAliveSeconds = configuration.getKeepAliveSeconds();
    defaultRequestTimeoutMilliseconds = configuration.getDefaultRequestTimeoutMilliseconds();
  }

  @Override
  public void start ()
    throws InterruptedException, IOException, CubbyOperationException {

    long start = System.currentTimeMillis();

    socketChannel = SocketChannel.open()
                      .setOption(StandardSocketOptions.SO_KEEPALIVE, true)
                      .setOption(StandardSocketOptions.TCP_NODELAY, true);
    socketChannel.configureBlocking(false);
    socketChannel.connect(memcachedHost.getAddress());

    while ((!socketChannel.finishConnect()) && (System.currentTimeMillis() - start) < connectionTimeoutMilliseconds) {
      Thread.sleep(100);
    }

    if (socketChannel.isConnectionPending()) {
      throw new ConnectionTimeoutException();
    }

    selectionKey = socketChannel.register(selector = Selector.open(), SelectionKey.OP_READ);

    requestWriter = new RequestWriter(socketChannel);
    responseReader = new ResponseReader(socketChannel);

    requestQueue.clear();
    responseQueue.clear();
    commandCounter.set(0L);

    if (authentication != null) {
      send(new AuthenticationCommand().setAuthentication(authentication), 0L);
    }
  }

  @Override
  public void stop ()
    throws InterruptedException {

    finished.set(true);
    terminationLatch.await();

    shutdown(false);
  }

  private void shutdown (boolean unexpected) {

    if (disconnected.compareAndSet(false, true)) {
      selectionKey.cancel();

      try {
        selector.close();
      } catch (IOException ioException) {
        LoggerManager.getLogger(NIOCubbyConnection.class).error(ioException);
      }

      try {
        socketChannel.close();
      } catch (IOException ioException) {
        LoggerManager.getLogger(NIOCubbyConnection.class).error(ioException);
      }

      if (unexpected) {
        connectionCoordinator.disconnect(memcachedHost);
      }
    }
  }

  @Override
  public Response send (Command command, Long timeoutSeconds)
    throws InterruptedException, IOException, CubbyOperationException {

    ClientRequestCallback requestCallback = new ClientRequestCallback(command);

    synchronized (requestQueue) {
      requestQueue.offer(new MissingLink(requestCallback, new CommandBuffer(commandCounter.getAndIncrement(), command.construct(keyTranslator))));
      selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
      selector.wakeup();
    }

    return requestCallback.getResult((timeoutSeconds == null) ? defaultRequestTimeoutMilliseconds : timeoutSeconds);
  }

  private MissingLink retrieveMissingLink ()
    throws CubbyOperationException {

    MissingLink missingLink;

    if ((missingLink = responseQueue.poll()) == null) {
      throw new CubbyOperationException("Desynchronized connection state");
    }

    return missingLink;
  }

  @Override
  public void run () {

    int invalidSelectionKeyCount = 0;
    int secondsSinceLastSelection = 0;

    try {
      while (!finished.get()) {
        try {
          if (selector.select(1000) <= 0) {
            if (++secondsSinceLastSelection > keepAliveSeconds) {
              secondsSinceLastSelection = 0;

              synchronized (requestQueue) {
                if (selectionKey.isValid()) {
                  requestQueue.offer(new MissingLink(new ServerRequestCallback(), new CommandBuffer(commandCounter.getAndIncrement(), new NoopCommand().construct(keyTranslator))));
                  selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
                  selector.wakeup();
                } else if (++invalidSelectionKeyCount >= 3) {
                  throw new InvalidSelectionKeyException();
                }
              }
            }
          } else {

            Iterator selectionKeyIter = selector.selectedKeys().iterator();

            while (selectionKeyIter.hasNext()) {

              SelectionKey selectionKey = selectionKeyIter.next();

              try {
                if (!selectionKey.isValid()) {
                  if (++invalidSelectionKeyCount >= 3) {
                    throw new InvalidSelectionKeyException();
                  }
                } else {
                  invalidSelectionKeyCount = 0;

                  if (selectionKey.isReadable()) {
                    if (responseReader.read()) {

                      boolean proceed = true;

                      do {

                        Response response;

                        try {
                          if ((response = responseReader.extract()) == null) {
                            proceed = false;
                          } else {

                            MissingLink missingLink;

                            if ((missingLink = retrieveMissingLink()).getRequestCallback() != null) {
                              missingLink.getRequestCallback().setResult(response);
                            }
                          }
                        } catch (IOException ioException) {

                          IOException exception = ioException;
                          MissingLink missingLink;

                          if ((missingLink = retrieveMissingLink()).getRequestCallback() != null) {
                            if (exception instanceof IncomprehensibleRequestException) {
                              exception = new IncomprehensibleRequestException(new String(missingLink.getCommandBuffer().getRequest(), StandardCharsets.UTF_8));
                            }

                            missingLink.getRequestCallback().setException(exception);
                          }
                        }
                      } while (proceed);
                    }
                  }
                  if (selectionKey.isWritable()) {
                    if (requestWriter.prepare()) {

                      MissingLink missingLink;

                      do {
                        synchronized (requestQueue) {
                          if ((missingLink = requestQueue.poll()) == null) {
                            selectionKey.interestOps(SelectionKey.OP_READ);
                          }
                        }

                        if (missingLink != null) {
                          responseQueue.add(missingLink);

                          if (!requestWriter.add(missingLink.getCommandBuffer())) {
                            break;
                          }
                        }
                      } while (missingLink != null);
                    }

                    requestWriter.write();
                  }
                }
              } finally {
                selectionKeyIter.remove();
              }
            }
          }
        } catch (IOException | CubbyOperationException exception) {
          LoggerManager.getLogger(NIOCubbyConnection.class).error(exception);

          finished.set(true);
          shutdown(true);
        }
      }
    } finally {
      terminationLatch.countDown();
    }
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy