001: /**
002: * com.mckoi.database.LockingMechanism 09 May 1998
003: *
004: * Mckoi SQL Database ( http://www.mckoi.com/database )
005: * Copyright (C) 2000, 2001, 2002 Diehl and Associates, Inc.
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License
009: * Version 2 as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful,
012: * but WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: * GNU General Public License Version 2 for more details.
015: *
016: * You should have received a copy of the GNU General Public License
017: * Version 2 along with this program; if not, write to the Free Software
018: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
019: *
020: * Change Log:
021: *
022: *
023: */package com.mckoi.database;
024:
025: import com.mckoi.debug.*;
026: import java.util.HashMap;
027:
028: /**
029: * This class represents a model for locking the tables in a database during
030: * any sequence of concurrent read/write accesses.
031: * <p>
032: * Every table in the database has an 'access_queue' that is generated the
033: * first time the table is accessed. When a read or write request happens,
034: * the thread and the type of access is put onto the top of the queue. When
035: * the read/write access to the table has completed, the access is removed
036: * from the queue.
037: * <p>
038: * An access to the table may be 'blocked' until other threads have completed
039: * their access of the table.
040: * <p>
041: * A table that has a 'read lock' can not be altered until the table object
042: * is released. A table that has a 'write lock' may not be read until the
043: * table object is released.
044: * <p>
045: * The general rules are:
046: * a) A read request can go ahead if there are no write request infront of
047: * this request in the access queue.
048: * b) A write request can go ahead if the write request is at the front of
049: * the access queue.
050: * <p>
051: * This class requires some for-sight to which tables will be read/written
052: * to. We must pass all tables being read/written in a single stage. This
053: * implies a 2 stage process, the 1st determining which tables are being
054: * accessed and the 2nd performing the actual operations.
055: * <p>
056: * Some operations such as creating and dropping and modifying the security
057: * tables may require that no threads interfere with the database state while
058: * the operation is occuring. This is handled through an 'Excluside Mode'.
059: * When an object calls the locking mechanism to switch into exclusive mode, it
060: * blocks until all access to the database are complete, then continues,
061: * blocking all other threads until the exclusive mode is cancelled.
062: * <p>
063: * The locking system, in simple terms, ensures that any multiple read
064: * operations will happen concurrently, however write operations block until
065: * all operations are complete.
066: * <p>
067: * SYNCHRONIZATION: This method implements some important concurrent models
068: * for ensuring that queries can never be corrupted.
069: * <p>
070: * @author Tobias Downer
071: */
072:
073: public final class LockingMechanism {
074:
075: /**
076: * Class statics. These are used in the 'setMode' method to request either
077: * shared or exclusive access to the database.
078: */
079: public final static int SHARED_MODE = 1;
080: public final static int EXCLUSIVE_MODE = 2;
081:
082: /**
083: * This Hashtable is a mapping from a 'DataTable' to the 'LockingQueue'
084: * object that is available for it.
085: */
086: private HashMap queues_map = new HashMap();
087:
088: /**
089: * This boolean is set as soon as a Thread requests to go into 'exclusive
090: * mode'.
091: */
092: private boolean in_exclusive_mode = false;
093:
094: /**
095: * This contains the number of Threads that have requested to go into
096: * 'shared mode'. It is incremented each time 'setMode(SHARED_MODE)' is
097: * called.
098: */
099: private int shared_mode = 0;
100:
101: /**
102: * The DebugLogger object that we log debug messages to.
103: */
104: private final DebugLogger debug;
105:
106: /**
107: * Constructor.
108: */
109: public LockingMechanism(DebugLogger logger) {
110: this .debug = logger;
111: }
112:
113: /**
114: * This is a helper function for returning the LockingQueue object for the
115: * DataTable object. If there has not previously been a queue instantiated
116: * for the table, it creates a new one and adds it to the Hashtable.
117: * <p>
118: * ISSUE: Not synchronized because we guarenteed to be called from a
119: * synchronized method right?
120: */
121: private LockingQueue getQueueFor(DataTable table) {
122: LockingQueue queue = (LockingQueue) queues_map.get(table);
123:
124: // If queue not in hashtable then create a new one and put it into mapping
125: if (queue == null) {
126: queue = new LockingQueue(table);
127: queues_map.put(table, queue);
128: }
129:
130: return queue;
131: }
132:
133: /**
134: * Resets this object so it may be reused. This will release all internal
135: * DataTable queues that are being kept.
136: */
137: public void reset() {
138:
139: synchronized (this ) {
140: // Check we are in exclusive mode,
141: if (!isInExclusiveMode()) {
142: // This is currently just a warning but should be upgraded to a
143: // full error.
144: debug
145: .writeException(new RuntimeException(
146: "Should not clear a "
147: + "LockingMechanism that's not in exclusive mode."));
148: }
149: queues_map.clear();
150: }
151:
152: }
153:
154: /**
155: * This method locks the given tables for either reading or writing. It
156: * puts the access locks in a queue for the given tables. This 'reserves'
157: * the rights for this thread to access the table in that way. This
158: * reservation can be used by the system to decide table accessability.
159: * <p>
160: * NOTE: ** IMPORTANT ** We must ensure that a single Thread can not create
161: * multiple table locks. Otherwise it will cause situations where deadlock
162: * can result.
163: * NOTE: ** IMPORTANT ** We must ensure that once a lock has occured, it
164: * is unlocked at a later time _no matter what happens_. Otherwise there
165: * will be situations where deadlock can result.
166: * NOTE: A LockHandle should not be given to another Thread.
167: * <p>
168: * SYNCHRONIZATION: This method is synchronized to ensure multiple additions
169: * to the locking queues can happen without interference.
170: */
171: public LockHandle lockTables(DataTable[] t_write, DataTable[] t_read) {
172:
173: // Set up the local constants.
174:
175: final int lock_count = t_read.length + t_write.length;
176: final LockHandle handle = new LockHandle(lock_count, debug);
177:
178: synchronized (this ) {
179:
180: Lock lock;
181: LockingQueue queue;
182: int queue_index;
183:
184: // Add read and write locks to cache and to the handle.
185:
186: for (int i = t_write.length - 1; i >= 0; --i) {
187: DataTable to_write_lock = t_write[i];
188: queue = getQueueFor(to_write_lock);
189: // slightly confusing: this will add lock to given table queue
190: lock = new Lock(Lock.WRITE, queue, debug);
191: handle.addLock(lock);
192:
193: debug.write(Lvl.INFORMATION, this ,
194: "[LockingMechanism] Locking for WRITE: "
195: + to_write_lock.getTableName());
196: }
197:
198: for (int i = t_read.length - 1; i >= 0; --i) {
199: DataTable to_read_lock = t_read[i];
200: queue = getQueueFor(to_read_lock);
201: // slightly confusing: this will add lock to given table queue
202: lock = new Lock(Lock.READ, queue, debug);
203: handle.addLock(lock);
204:
205: debug.write(Lvl.INFORMATION, this ,
206: "[LockingMechanism] Locking for READ: "
207: + to_read_lock.getTableName());
208: }
209:
210: }
211:
212: debug.write(Lvl.INFORMATION, this , "Locked Tables");
213:
214: return handle;
215:
216: }
217:
218: /**
219: * Unlocks the tables that were previously locked by the 'lockTables' method.
220: * It is required that this method is called after the table references made
221: * by a query are released (set to null or forgotten). This usually means
222: * _after_ the result set has been written to the client.
223: * SYNCHRONIZATION: This method is synchronized so concurrent unlocking
224: * can not corrupt the queues.
225: */
226: public void unlockTables(LockHandle handle) {
227: synchronized (this ) {
228: handle.unlockAll();
229: }
230: debug.write(Lvl.INFORMATION, this , "UnLocked Tables");
231: }
232:
233: /**
234: * Returns true if we are locked into exclusive mode.
235: */
236: public synchronized boolean isInExclusiveMode() {
237: return in_exclusive_mode;
238: }
239:
240: /**
241: * This method _must_ be called before a threads initial access to a Database
242: * object. It registers whether the preceding database accesses will be in
243: * an 'exclusive mode' or a 'shared mode'. In shared mode, any number of
244: * threads are able to access the database. In exclusive, the current thread
245: * may be the only one that may access the database.
246: * On requesting exclusive mode, it blocks until exclusive mode is available.
247: * On requesting shared mode, it blocks only if currently in exclusive mode.
248: * NOTE: 'exclusive mode' should be used only in system maintenance type
249: * operations such as creating and dropping tables from the database.
250: */
251: public synchronized void setMode(int mode) {
252:
253: // If currently in exclusive mode, block until not.
254:
255: while (in_exclusive_mode == true) {
256: try {
257: // System.out.println("Waiting because in exclusive lock.");
258: wait();
259: // System.out.println("Finish: Waiting because in exclusive lock.");
260: } catch (InterruptedException e) {
261: }
262: }
263:
264: if (mode == EXCLUSIVE_MODE) {
265:
266: // Set this thread to exclusive mode, and wait until all shared modes
267: // have completed.
268:
269: in_exclusive_mode = true;
270: while (shared_mode > 0) {
271: try {
272: // System.out.println("Waiting on exclusive lock: " + shared_mode);
273: wait();
274: // System.out.println("Finish: Waiting on exclusive lock: " + shared_mode);
275: } catch (InterruptedException e) {
276: }
277: }
278:
279: debug.write(Lvl.INFORMATION, this ,
280: "Locked into ** EXCLUSIVE MODE **");
281:
282: } else if (mode == SHARED_MODE) {
283:
284: // Increase the threads counter that are in shared mode.
285:
286: ++shared_mode;
287:
288: debug.write(Lvl.INFORMATION, this ,
289: "Locked into SHARED MODE");
290:
291: } else {
292: throw new Error("Invalid mode");
293: }
294: }
295:
296: /**
297: * This must be called when the calls to a Database object have finished.
298: * It 'finishes' the mode that the locking mechanism was set into by the
299: * call to the 'setMode' method.
300: * NOTE: ** IMPORTANT ** This method __MUST__ be guarenteed to be called some
301: * time after the 'setMode' method. Otherwise deadlock.
302: */
303: public synchronized void finishMode(int mode) {
304: if (mode == EXCLUSIVE_MODE) {
305: in_exclusive_mode = false;
306: notifyAll();
307:
308: debug.write(Lvl.INFORMATION, this ,
309: "UnLocked from ** EXCLUSIVE MODE **");
310:
311: } else if (mode == SHARED_MODE) {
312: --shared_mode;
313: if (shared_mode == 0 && in_exclusive_mode) {
314: notifyAll();
315: } else if (shared_mode < 0) {
316: shared_mode = 0;
317: notifyAll();
318: throw new RuntimeException(
319: "Too many 'finishMode(SHARED_MODE)' calls");
320: }
321:
322: debug.write(Lvl.INFORMATION, this ,
323: "UnLocked from SHARED MODE");
324:
325: } else {
326: throw new Error("Invalid mode");
327: }
328: }
329:
330: }
|