001: /**
002: * Sequoia: Database clustering technology.
003: * Copyright (C) 2002-2004 French National Institute For Research In Computer
004: * Science And Control (INRIA).
005: * Copyright (C) 2005 AmicoSoft, Inc. dba Emic Networks
006: * Contact: sequoia@continuent.org
007: *
008: * Licensed under the Apache License, Version 2.0 (the "License");
009: * you may not use this file except in compliance with the License.
010: * You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing, software
015: * distributed under the License is distributed on an "AS IS" BASIS,
016: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
017: * See the License for the specific language governing permissions and
018: * limitations under the License.
019: *
020: * Initial developer(s): Emmanuel Cecchet.
021: * Contributor(s): Jean-Bernard van Zuylen.
022: */package org.continuent.sequoia.controller.scheduler.raidb1;
023:
024: import java.sql.SQLException;
025: import java.util.ArrayList;
026:
027: import org.continuent.sequoia.common.exceptions.RollbackException;
028: import org.continuent.sequoia.common.sql.schema.DatabaseSchema;
029: import org.continuent.sequoia.common.sql.schema.DatabaseTable;
030: import org.continuent.sequoia.common.xml.DatabasesXmlTags;
031: import org.continuent.sequoia.controller.requestmanager.RAIDbLevels;
032: import org.continuent.sequoia.controller.requests.AbstractWriteRequest;
033: import org.continuent.sequoia.controller.requests.ParsingGranularities;
034: import org.continuent.sequoia.controller.requests.SelectRequest;
035: import org.continuent.sequoia.controller.requests.StoredProcedure;
036: import org.continuent.sequoia.controller.scheduler.AbstractScheduler;
037: import org.continuent.sequoia.controller.scheduler.schema.SchedulerDatabaseSchema;
038: import org.continuent.sequoia.controller.scheduler.schema.SchedulerDatabaseTable;
039: import org.continuent.sequoia.controller.scheduler.schema.TransactionExclusiveLock;
040:
041: /**
042: * This scheduler provides transaction level scheduling for RAIDb-1 controllers.
043: * Each write takes a lock on the table it affects. All following writes are
044: * blocked until the transaction of the first write completes. This scheduler
045: * automatically detects simple deadlocks and rollbacks the transaction inducing
046: * the deadlock. Note that transitive deadlocks (involving more than 2 tables
047: * are not detected).
048: *
049: * @author <a href="mailto:Emmanuel.Cecchet@inria.fr">Emmanuel Cecchet </a>
050: * @author <a href="mailto:jbvanzuylen@transwide.com">Jean-Bernard van Zuylen
051: * </a>
052: * @version 1.0
053: * @deprecated since Sequoia 2.2
054: */
055: public class RAIDb1OptimisticTransactionLevelScheduler extends
056: AbstractScheduler {
057:
058: //
059: // How the code is organized ?
060: //
061: // 1. Member variables
062: // 2. Constructor
063: // 3. Request handling
064: // 4. Transaction management
065: // 5. Debug/Monitoring
066: //
067:
068: private SchedulerDatabaseSchema schedulerDatabaseSchema = null;
069:
070: //
071: // Constructor
072: //
073:
074: /**
075: * Creates a new Optimistic Transaction Level Scheduler
076: */
077: public RAIDb1OptimisticTransactionLevelScheduler() {
078: super (RAIDbLevels.RAIDb1, ParsingGranularities.TABLE);
079: }
080:
081: //
082: // Request Handling
083: //
084:
085: /**
086: * Sets the <code>DatabaseSchema</code> of the current virtual database.
087: * This is only needed by some schedulers that will have to define their own
088: * scheduler schema
089: *
090: * @param dbs a <code>DatabaseSchema</code> value
091: * @see org.continuent.sequoia.controller.scheduler.schema.SchedulerDatabaseSchema
092: */
093: public synchronized void setDatabaseSchema(DatabaseSchema dbs) {
094: if (schedulerDatabaseSchema == null) {
095: logger.info("Setting new database schema");
096: schedulerDatabaseSchema = new SchedulerDatabaseSchema(dbs);
097: } else { // Schema is updated, compute the diff !
098: SchedulerDatabaseSchema newSchema = new SchedulerDatabaseSchema(
099: dbs);
100: ArrayList tables = schedulerDatabaseSchema.getTables();
101: ArrayList newTables = newSchema.getTables();
102: if (newTables == null) { // New schema is empty (no backend is active anymore)
103: logger.info("Removing all tables.");
104: schedulerDatabaseSchema = null;
105: return;
106: }
107:
108: // Remove extra-tables
109: for (int i = 0; i < tables.size(); i++) {
110: SchedulerDatabaseTable t = (SchedulerDatabaseTable) tables
111: .get(i);
112: if (!newSchema.hasTable(t.getName())) {
113: schedulerDatabaseSchema.removeTable(t);
114: if (logger.isInfoEnabled())
115: logger.info("Removing table " + t.getName());
116: }
117: }
118:
119: // Add missing tables
120: int size = newTables.size();
121: for (int i = 0; i < size; i++) {
122: SchedulerDatabaseTable t = (SchedulerDatabaseTable) newTables
123: .get(i);
124: if (!schedulerDatabaseSchema.hasTable(t.getName())) {
125: schedulerDatabaseSchema.addTable(t);
126: if (logger.isInfoEnabled())
127: logger.info("Adding table " + t.getName());
128: }
129: }
130: }
131: }
132:
133: /**
134: * Merge the given <code>DatabaseSchema</code> with the current one.
135: *
136: * @param dbs a <code>DatabaseSchema</code> value
137: * @see org.continuent.sequoia.controller.scheduler.schema.SchedulerDatabaseSchema
138: */
139: public void mergeDatabaseSchema(DatabaseSchema dbs) {
140: try {
141: logger.info("Merging new database schema");
142: schedulerDatabaseSchema
143: .mergeSchema(new SchedulerDatabaseSchema(dbs));
144: } catch (Exception e) {
145: logger.error("Error while merging new database schema", e);
146: }
147: }
148:
149: /**
150: * Additionally to scheduling the request, this method replaces the SQL Date
151: * macros such as now() with the current date.
152: *
153: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#scheduleNonSuspendedReadRequest(SelectRequest)
154: */
155: public final void scheduleNonSuspendedReadRequest(
156: SelectRequest request) throws SQLException {
157: }
158:
159: /**
160: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#readCompletedNotify(SelectRequest)
161: */
162: public final void readCompletedNotify(SelectRequest request) {
163: }
164:
165: /**
166: * Additionally to scheduling the request, this method replaces the SQL Date
167: * macros such as now() with the current date.
168: *
169: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#scheduleWriteRequest(AbstractWriteRequest)
170: */
171: public void scheduleNonSuspendedWriteRequest(
172: AbstractWriteRequest request) throws SQLException,
173: RollbackException {
174: if (request.isCreate()) {
175: return;
176: }
177:
178: SchedulerDatabaseTable t = schedulerDatabaseSchema
179: .getTable(request.getTableName());
180: if (t == null) {
181: String msg = "No table found for request "
182: + request.getId();
183: logger.error(msg);
184: throw new SQLException(msg);
185: }
186:
187: // Deadlock detection
188: TransactionExclusiveLock tableLock = t.getLock();
189: if (!request.isAutoCommit()) {
190: synchronized (this ) {
191: if (tableLock.isLocked()) { // Is the lock owner blocked by a lock we already own?
192: long owner = tableLock.getLocker();
193: long us = request.getTransactionId();
194: if (owner != us) { // Parse all tables
195: ArrayList tables = schedulerDatabaseSchema
196: .getTables();
197: ArrayList weAreblocking = new ArrayList();
198: int size = tables.size();
199: for (int i = 0; i < size; i++) {
200: SchedulerDatabaseTable table = (SchedulerDatabaseTable) tables
201: .get(i);
202: if (table == null)
203: continue;
204: TransactionExclusiveLock lock = table
205: .getLock();
206: // Are we the lock owner ?
207: if (lock.isLocked()) {
208: if (lock.getLocker() == us) {
209: // Is 'owner' in the list of the blocked transactions?
210: if (lock.isWaiting(owner)) { // Deadlock detected, we must rollback
211: releaseLocks(us);
212: throw new RollbackException(
213: "Deadlock detected, rollbacking transaction "
214: + us);
215: } else
216: weAreblocking.addAll(lock
217: .getWaitingList());
218: }
219: }
220: }
221: } else { // We are the lock owner
222: return;
223: }
224: } else { // Lock is free, take it in the synchronized block
225: acquireLockAndSetRequestId(request, tableLock);
226: return;
227: }
228: }
229: }
230:
231: acquireLockAndSetRequestId(request, tableLock);
232: }
233:
234: private void acquireLockAndSetRequestId(
235: AbstractWriteRequest request,
236: TransactionExclusiveLock tableLock) throws SQLException {
237: // Acquire the lock
238: if (tableLock.acquire(request)) {
239: if (logger.isDebugEnabled())
240: logger.debug("Request " + request.getId()
241: + " scheduled for write (" + getPendingWrites()
242: + " pending writes)");
243: } else {
244: if (logger.isWarnEnabled())
245: logger
246: .warn("Request " + request.getId()
247: + " timed out (" + request.getTimeout()
248: + " s)");
249: throw new SQLException("Timeout (" + request.getTimeout()
250: + ") for request: " + request.getId());
251: }
252: }
253:
254: /**
255: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#notifyWriteCompleted(AbstractWriteRequest)
256: */
257: public final void notifyWriteCompleted(AbstractWriteRequest request) {
258: if (request.isCreate()) { // Add table to schema
259: if (logger.isDebugEnabled())
260: logger.debug("Adding table '" + request.getTableName()
261: + "' to scheduler schema");
262: synchronized (this ) {
263: schedulerDatabaseSchema
264: .addTable(new SchedulerDatabaseTable(
265: new DatabaseTable(request
266: .getTableName())));
267: }
268: } else if (request.isDrop()) { // Drop table from schema
269: if (logger.isDebugEnabled())
270: logger.debug("Removing table '"
271: + request.getTableName()
272: + "' to scheduler schema");
273: synchronized (this ) {
274: schedulerDatabaseSchema
275: .removeTable(schedulerDatabaseSchema
276: .getTable(request.getTableName()));
277: }
278: return;
279: }
280:
281: // Requests outside transaction delimiters must release the lock
282: // as soon as they have executed
283: if (request.isAutoCommit()) {
284: SchedulerDatabaseTable t = schedulerDatabaseSchema
285: .getTable(request.getTableName());
286: if (t == null) {
287: String msg = "No table found to release lock for request "
288: + request.getId();
289: logger.error(msg);
290: } else
291: t.getLock().release();
292: }
293: }
294:
295: /**
296: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#scheduleNonSuspendedStoredProcedure(org.continuent.sequoia.controller.requests.StoredProcedure)
297: */
298: public void scheduleNonSuspendedStoredProcedure(StoredProcedure proc)
299: throws SQLException, RollbackException {
300: throw new SQLException(
301: "Stored procedures are not supported by the RAIDb-1 optimistic transaction level scheduler.");
302: }
303:
304: /**
305: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#notifyStoredProcedureCompleted(org.continuent.sequoia.controller.requests.StoredProcedure)
306: */
307: public void notifyStoredProcedureCompleted(StoredProcedure proc) {
308: // We should never execute here since scheduleNonSuspendedStoredProcedure
309: // should have failed prior calling us
310: throw new RuntimeException(
311: "Stored procedures are not supported by the RAIDb-1 optimistic transaction level scheduler.");
312: }
313:
314: //
315: // Transaction Management
316: //
317:
318: /**
319: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#commitTransaction(long)
320: */
321: protected final void commitTransaction(long transactionId) {
322: releaseLocks(transactionId);
323: }
324:
325: /**
326: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#rollbackTransaction(long)
327: */
328: protected final void rollbackTransaction(long transactionId) {
329: releaseLocks(transactionId);
330: }
331:
332: /**
333: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#rollbackTransaction(long,
334: * String)
335: */
336: protected final void rollbackTransaction(long transactionId,
337: String savepointName) {
338: }
339:
340: /**
341: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#setSavepointTransaction(long,
342: * String)
343: */
344: protected final void setSavepointTransaction(long transactionId,
345: String name) {
346: }
347:
348: /**
349: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#releaseSavepointTransaction(long,
350: * String)
351: */
352: protected final void releaseSavepointTransaction(
353: long transactionId, String name) {
354: }
355:
356: /**
357: * Release all locks we may own on tables.
358: *
359: * @param transactionId id of the transaction that releases the locks
360: */
361: private synchronized void releaseLocks(long transactionId) {
362: ArrayList tables = schedulerDatabaseSchema.getTables();
363: int size = tables.size();
364: for (int i = 0; i < size; i++) {
365: SchedulerDatabaseTable t = (SchedulerDatabaseTable) tables
366: .get(i);
367: if (t == null)
368: continue;
369: TransactionExclusiveLock lock = t.getLock();
370: // Are we the lock owner ?
371: if (lock.isLocked())
372: if (lock.getLocker() == transactionId)
373: lock.release();
374: }
375: }
376:
377: //
378: // Debug/Monitoring
379: //
380:
381: /**
382: * @see org.continuent.sequoia.controller.scheduler.AbstractScheduler#getXmlImpl()
383: */
384: public String getXmlImpl() {
385: return "<" + DatabasesXmlTags.ELT_RAIDb1Scheduler + " "
386: + DatabasesXmlTags.ATT_level + "=\""
387: + DatabasesXmlTags.VAL_optimisticTransaction + "\"/>";
388: }
389: }
|