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

com.navercorp.nbasearc.gcp.VirtualConnection Maven / Gradle / Ivy

The newest version!
/*
 * Copyright 2015 NAVER Corp.
 *
 * 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.navercorp.nbasearc.gcp;

import static com.navercorp.nbasearc.gcp.ErrorCode.*;

import java.util.ArrayDeque;
import java.util.Queue;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.SettableFuture;
import com.navercorp.redis.cluster.gateway.AffinityState;

import io.netty.util.concurrent.ScheduledFuture;

public class VirtualConnection {

    private static final Logger log = LoggerFactory.getLogger(VirtualConnection.class);

    private final GatewayConnectionPool gcp;
    private final int qSize;
    private final Semaphore qSpace;
    private final AtomicBoolean closed;
    private final CloseJob job;

    private int hash;
    private AffinityState affinity;
    private Connection con;
    private boolean pipelineMode;

    VirtualConnection(GatewayConnectionPool gcp, int qSize) {
        this.gcp = gcp;
        this.qSize = qSize;
        this.qSpace = new Semaphore(qSize);
        this.closed = new AtomicBoolean();
        this.job = new CloseJob();
        this.con = new Connection(qSize);
    }
    
    public void allocPc(int hash, AffinityState affinity, boolean pipelineMode) {
        this.hash = hash;
        this.affinity = affinity;
        synchronized (con) {
            this.con.pc = gcp.bestCon(hash, affinity);
        }
        this.pipelineMode = pipelineMode;
    }

    public ListenableFuture close() {
        if (closed.getAndSet(true)) {
            return job.sf;
        }
        job.self = gcp.scheduleAtFixedRate(job, 10, 10, TimeUnit.MILLISECONDS);
        return job.sf;
    }

    private class CloseJob implements Runnable {
        private volatile ScheduledFuture self;
        private final SettableFuture sf = SettableFuture.create();

        @Override
        public void run() {
            synchronized (con) {
                if (!con.hasPendingResponse()) {
                    if (con.pc != null) {
                        synchronized (con.pc) {
                            con.pc.decreaseReferenceCount();
                        }
                        con.pc = null;
                    }
                    
                    self.cancel(false);
                    VirtualConnection.this.destroy();
                    sf.set(null);
                }
            }
        }
    }

    private void destroy() {
        gcp.delVc(VirtualConnection.this);
        con.destroy();
    }
    
    public void freePc() {
        synchronized (con) {
            if (con.pc != null) {
                synchronized (con.pc) {
                    con.pc.decreaseReferenceCount();
                }
                con.pc = null;
            }
        }
    }
    
    void reallocIdlePc(PhysicalConnection pc) {
        synchronized (con) {
            if (con.pc != null && con.pc == pc && con.hasPendingResponse() == false
                    && con.hasPendingRequest() == false) {
                if (con.pc != null) {
                    synchronized (con.pc) {
                        con.pc.decreaseReferenceCount();
                    }
                    con.pc = null;
                }
                con.pc = gcp.bestCon(hash, affinity);
            }
        }
    }

    public void request(final byte[] cmd, final int timeout, final RequestCallback requestCallback) {
        final Request rqst = Request.userRequest(cmd, timeout, requestCallback, this);

        if (pipelineMode) {
            requestPipelineMode(rqst);
        } else {
            con.execute(rqst);
        }
    }

    public boolean isConnected() {
        PhysicalConnection pc = con.pc;
        if (pc == null) {
            return false;
        } else {
            return pc.getState() == PhysicalConnection.State.CONNECTED;
        }
    }
    
    private void requestPipelineMode(final Request rqst) {
        // Acquire queue space
        try {
            qSpace.acquire();
        } catch (InterruptedException e) {
            log.error("Interrupted while acquiring qSpace.", e);
            rqst.callback.onResponse(null, INTERNAL_ERROR);
            qSpace.release();
            return;
        }

        synchronized (con) {
            if (con.getFirstError() != null) { // No more requests are permitted if there an error.
                if (con.hasPendingResponse()) {
                    con.addPendingRequest(rqst);
                } else {
                    con.flushPendigRequests(con.getFirstError());
                    rqst.callback.onResponse(null, con.getFirstError());
                    qSpace.release();
                }
                return;
            }

            if (con.hasPendingRequest()) {
                con.addPendingRequest(rqst);
                return;
            }
            
            boolean pending = false;
            if ((con.pc == null || con.pc.getState() != PhysicalConnection.State.CONNECTED)
                    && con.hasPendingResponse()) {
                con.addPendingRequest(rqst);
                pending = true;
            }
            
            if (con.checkAndReallocPc()) {
                con.flushPendigRequests(ErrorCode.OK);
                
                if (con.pc == null && pending == false) {
                    rqst.callback.onResponse(null, ErrorCode.NO_AVAILABLE_CONNECTION);
                    qSpace.release();
                    return;
                }
            }
            
            if (pending == false) {
                con.incrementWaitRespCnt(1);
                con.execute(rqst);
            }
        }
    }

    void onResponse(Request rqst, byte[] response, ErrorCode errCode) {
        if (pipelineMode) {
            onResponsePipelineMode(rqst, response, errCode);
        } else {
            rqst.callback.onResponse(response, errCode);
        }
    }
    
    private void onResponsePipelineMode(Request rqst, byte[] response, ErrorCode errCode) {
        // Check and reallocate physical connection
        synchronized (con) {
            con.decrementWaitRespCnt();
            
            if (errCode.isError() && con.getFirstError() == null) {
                con.setFirstError(errCode);
            }
            
            if (con.checkAndReallocPc()) {
                con.flushPendigRequests(ErrorCode.OK);
            }
        }

        // Response callback
        rqst.callback.onResponse(response, errCode);
        qSpace.release();
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append("[hash: ").append(hash).append(", addr: ").append(con.pc.toString()).append(", wait: ")
                .append(con.waitRespCnt.get()).append("]").toString();
        return sb.toString();
    }

    /**
     * All accesses to this class must be performed in a synchronized(Connection) block.
     *
     */
    private class Connection {
        private PhysicalConnection pc;
        private AtomicLong waitRespCnt;
        private Queue pending;
        private final int qSize;
        private ErrorCode firstError;

        private Connection(int qSize) {
            this.pc = null;
            this.waitRespCnt = new AtomicLong(0);
            this.pending = new ArrayDeque(qSize);
            this.qSize = qSize;
            this.setFirstError(null);
        }

        public void destroy() {
            final int p = pending.size();
            assert hasPendingResponse() == false && pending.isEmpty(): 
                "Connection is still used. waitRespCnt: " + waitRespCnt.get() + ", pending: " + p;
            pending.clear();
        }

        private void execute(Request rqst) {
            rqst.setPc(pc);
            
            if (rqst.getState() != Request.State.QUEUING) {
                log.error("{} of request cannot be put into pipeline.", rqst.getState());
                VirtualConnection.this.onResponse(rqst, null, INTERNAL_ERROR);
                return;
            }
            
            if (pc == null) {
                VirtualConnection.this.onResponse(rqst, null, NO_AVAILABLE_CONNECTION);
                return;
            }

            pc.execute(rqst);
        }

        private boolean checkAndReallocPc() {
            if (needRellocPc()) {
                if (pc != null) {
                    pc.decreaseReferenceCount();
                }
                
                pc = gcp.bestCon(hash, affinity);
                return true;
            }
            return false;
        }

        private boolean needRellocPc() {
            if (pc == null) {
                return true;
            }

            if (pc.getState() != PhysicalConnection.State.CONNECTED && hasPendingResponse() == false) {
                return true;
            }

            return false;
        }

        private void flushPendigRequests(ErrorCode errCode) {
            if (errCode != ErrorCode.OK) {
                while (!pending.isEmpty()) {
                    pending.poll().callback.onResponse(null, errCode);
                    qSpace.release();
                }
                return;
            }
            
            if (pc == null) {
                while (!pending.isEmpty()) {
                    Request rqst = pending.poll();
                    rqst.callback.onResponse(null, ErrorCode.NO_AVAILABLE_CONNECTION);
                    qSpace.release();
                }
                return;
            }

            for (Request rqst : pending) {
                rqst.setPc(pc);
            }

            incrementWaitRespCnt(pending.size());
            
            BulkRequest br = new BulkRequest(pending, pc);
            pc.execute(br);
            pending = new ArrayDeque(qSize);
        }
        
        private boolean hasPendingResponse() {
            return waitRespCnt.get() != 0;
        }

        private void incrementWaitRespCnt(long delta) {
            waitRespCnt.addAndGet(delta);
        }

        private void decrementWaitRespCnt() {
            final long cnt = waitRespCnt.decrementAndGet();
            assert cnt >= 0 : "Invalid state of VirtualConnection. respWaitCnt: " + cnt;
        }

        private void addPendingRequest(Request rqst) {
            pending.add(rqst);
        }
        
        private boolean hasPendingRequest() {
            return pending.isEmpty() == false;
        }

        private ErrorCode getFirstError() {
            return firstError;
        }

        private void setFirstError(ErrorCode error) {
            this.firstError = error;
        }
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy