/***************************************************************************** * Copyright (C) 2008 EnterpriseDB Corporation. * Copyright (C) 2011 Stado Global Development Group. * * This file is part of Stado. * * Stado is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Stado 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 for more details. * * You should have received a copy of the GNU General Public License * along with Stado. If not, see <http://www.gnu.org/licenses/>. * * You can find Stado at http://www.stado.us * ****************************************************************************/ package org.postgresql.stado.metadata.scheduler; import java.util.Map; import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; import org.postgresql.stado.common.util.Property; import org.postgresql.stado.engine.XDBSessionContext; /** * Request handling sequence: 1. Request is enqueued. XDBSessionContext * maintains a queue of requests, but while we does not support multiple * concurrent requests within one connection there is at most one entry in the * queue. 2. Request is scheduled. First request in queue is registered with * scheduler. If the request is enqueued and queue has been empty it is * scheduled immediately. 3. Scheduler notifies XDBSessionContext that request * can be executed. XDBSessionContext tries and acquires locks needed by calling * LockManager, then reports result back to Scheduler. There are three possible * results: a. LockManager succeeded and locks are acquired. Request is removed * from Scheduler. Go to Step 4. b. LockManager failed (there are conflicting * locks) and no locks are acquired. Scheduler moves to another session, request * is waiting for next time. c. Exception is thrown (deadlock is detected) and * no locks are acquired. Request is removed from Scheduler, error response is * written to client. 4. Scheduler allocates thread and executes request * prepared on Step 3 within it. 5. Locks are released by calling LockManager. * It is possible that LockManager does not release the locks immediately. If * there is active transaction LockManager will held the locks until transaction * is committed or rolled back. 6. XDBSessionContext is cleaned and become ready * for next request. If there is one in the queue it is scheduled. */ public class Scheduler implements Runnable { private static final int QUERY_LIMIT = Property.getInt( "xdb.jdbc.pool.query.count", Property.getInt( "xdb.jdbc.pool.maxsize", 10)); private static final int LARGE_QUERY_LIMIT = Property.getInt( "xdb.jdbc.pool.largequery.count", 2); private final TreeMap<RequestCost, XDBSessionContext> serviceQueue = new TreeMap<RequestCost, XDBSessionContext>(); private final AtomicInteger queryCount; private final AtomicInteger largeQueryCount; public Scheduler() { queryCount = new AtomicInteger(0); largeQueryCount = new AtomicInteger(0); } public void addRequest(RequestCost cost, XDBSessionContext client) { boolean fallThru; synchronized (this) { fallThru = serviceQueue.isEmpty(); serviceQueue.put(cost, client); notifyAll(); } if (!fallThru) { synchronized (client) { try { client.wait(1000); } catch (InterruptedException ie) { // ignore } } } queryCount.incrementAndGet(); if (cost.isLarge()) { largeQueryCount.incrementAndGet(); } } public XDBSessionContext removeRequest(RequestCost cost) { queryCount.decrementAndGet(); if (cost.isLarge()) { largeQueryCount.decrementAndGet(); } XDBSessionContext client; synchronized (this) { client = serviceQueue.remove(cost); notifyAll(); } if (client != null) { synchronized (client) { client.notifyAll(); } } return client; } public void holdRequest(RequestCost cost) { XDBSessionContext client = removeRequest(cost); if (client != null) { synchronized (this) { try { wait(1000); } catch (InterruptedException ie) { // ignore } } addRequest(cost, client); } } public synchronized void run() { while (true) { for (Map.Entry<RequestCost, XDBSessionContext> entry : serviceQueue .entrySet()) { if (QUERY_LIMIT <= queryCount.get()) { break; } if (entry.getKey().isLarge() && LARGE_QUERY_LIMIT <= largeQueryCount.get()) { continue; } synchronized (entry.getValue()) { entry.getValue().notifyAll(); } } try { wait(1000); } catch (InterruptedException ie) { // ignore } } } }