001: /*
002: * Copyright (c) 1998-2008 Caucho Technology -- all rights reserved
003: *
004: * This file is part of Resin(R) Open Source
005: *
006: * Each copy or derived work must preserve the copyright notice and this
007: * notice unmodified.
008: *
009: * Resin Open Source is free software; you can redistribute it and/or modify
010: * it under the terms of the GNU General Public License as published by
011: * the Free Software Foundation; either version 2 of the License, or
012: * (at your option) any later version.
013: *
014: * Resin Open Source is distributed in the hope that it will be useful,
015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
017: * of NON-INFRINGEMENT. See the GNU General Public License for more
018: * details.
019: *
020: * You should have received a copy of the GNU General Public License
021: * along with Resin Open Source; if not, write to the
022: *
023: * Free Software Foundation, Inc.
024: * 59 Temple Place, Suite 330
025: * Boston, MA 02111-1307 USA
026: *
027: * @author Scott Ferguson
028: */
029:
030: package com.caucho.db.store;
031:
032: import com.caucho.log.Log;
033: import com.caucho.management.server.AbstractManagedObject;
034: import com.caucho.management.server.BlockManagerMXBean;
035: import com.caucho.util.L10N;
036: import com.caucho.util.LongKeyLruCache;
037:
038: import java.io.IOException;
039: import java.util.ArrayList;
040: import java.util.Iterator;
041: import java.util.logging.Level;
042: import java.util.logging.Logger;
043:
044: /**
045: * Manages the block cache
046: */
047: public final class BlockManager extends AbstractManagedObject implements
048: BlockManagerMXBean {
049: private static final Logger log = Log.open(BlockManager.class);
050: private static final L10N L = new L10N(BlockManager.class);
051:
052: private static BlockManager _staticManager;
053:
054: private final byte[] _storeMask = new byte[8192];
055: private final LongKeyLruCache<Block> _blockCache;
056:
057: private final ArrayList<Block> _writeQueue = new ArrayList<Block>();
058: private int _writeQueueMax = 32;
059:
060: private BlockManager(int capacity) {
061: _blockCache = new LongKeyLruCache<Block>(capacity);
062:
063: // the first store id is not available to allow for tests for zero.
064: _storeMask[0] |= 1;
065:
066: registerSelf();
067:
068: BlockManagerWriter writer = new BlockManagerWriter();
069: Thread thread = new Thread(writer, "resin-block-manager-writer");
070: thread.setDaemon(true);
071:
072: thread.start();
073: }
074:
075: /**
076: * Returns the block manager, ensuring a minimum number of entries.
077: */
078: public static synchronized BlockManager create(int minEntries) {
079: if (_staticManager == null)
080: _staticManager = new BlockManager(minEntries);
081:
082: _staticManager.ensureCapacity(minEntries);
083:
084: return _staticManager;
085: }
086:
087: public static BlockManager getBlockManager() {
088: return _staticManager;
089: }
090:
091: /**
092: * Ensures the cache has a minimum number of blocks.
093: *
094: * @param minCapacity the minimum capacity in blocks
095: */
096: public void ensureCapacity(int minCapacity) {
097: _blockCache.ensureCapacity(minCapacity);
098: }
099:
100: /**
101: * Allocates a store id.
102: */
103: public int allocateStoreId() {
104: synchronized (_storeMask) {
105: for (int i = 0; i < _storeMask.length; i++) {
106: int mask = _storeMask[i];
107:
108: if (mask != 0xff) {
109: for (int j = 0; j < 8; j++) {
110: if ((mask & (1 << j)) == 0) {
111: _storeMask[i] |= (1 << j);
112:
113: return 8 * i + j;
114: }
115: }
116: }
117: }
118:
119: throw new IllegalStateException(L.l("All store ids used."));
120: }
121: }
122:
123: /**
124: * Frees blocks with the given store.
125: */
126: public void flush(Store store) {
127: ArrayList<Block> dirtyBlocks = null;
128:
129: synchronized (_blockCache) {
130: Iterator<Block> values = _blockCache.values();
131:
132: while (values.hasNext()) {
133: Block block = values.next();
134:
135: if (block != null && block.getStore() == store) {
136: if (block.isDirty()) {
137: if (dirtyBlocks == null)
138: dirtyBlocks = new ArrayList<Block>();
139:
140: dirtyBlocks.add(block);
141: }
142: }
143: }
144: }
145:
146: for (int i = 0; dirtyBlocks != null && i < dirtyBlocks.size(); i++) {
147: Block block = dirtyBlocks.get(i);
148:
149: try {
150: synchronized (block) {
151: block.write();
152: }
153: } catch (IOException e) {
154: log.log(Level.FINER, e.toString(), e);
155: }
156: }
157: }
158:
159: /**
160: * Frees blocks with the given store.
161: */
162: public void freeStore(Store store) {
163: ArrayList<Block> removeBlocks = new ArrayList<Block>();
164:
165: synchronized (_blockCache) {
166: Iterator<Block> iter = _blockCache.values();
167:
168: while (iter.hasNext()) {
169: Block block = iter.next();
170:
171: if (block != null && block.getStore() == store)
172: removeBlocks.add(block);
173: }
174: }
175:
176: for (Block block : removeBlocks) {
177: _blockCache.remove(block.getBlockId());
178: }
179:
180: synchronized (_writeQueue) {
181: while (_writeQueue.size() > 0) {
182: try {
183: _writeQueue.wait();
184: } catch (InterruptedException e) {
185: }
186: }
187: }
188: }
189:
190: /**
191: * Frees a store id.
192: */
193: public void freeStoreId(int storeId) {
194: synchronized (_storeMask) {
195: if (storeId <= 0)
196: throw new IllegalArgumentException(String
197: .valueOf(storeId));
198:
199: _storeMask[storeId / 8] &= ~(1 << storeId % 8);
200: }
201: }
202:
203: /**
204: * Gets the table's block.
205: */
206: Block getBlock(Store store, long blockId) {
207: // XXX: proper handling of the synchronized is tricky because
208: // the LRU dirty write might have timing issues
209:
210: Block block = _blockCache.get(blockId);
211:
212: while (block == null || !block.allocate()) {
213: // Find any matching block in the process of being written
214: Block dirtyBlock = null;
215: synchronized (_writeQueue) {
216: int size = _writeQueue.size();
217:
218: for (int i = 0; i < size; i++) {
219: dirtyBlock = _writeQueue.get(i);
220:
221: if (dirtyBlock.getBlockId() == blockId
222: && dirtyBlock.allocate()) {
223: break;
224: } else
225: dirtyBlock = null;
226: }
227: }
228:
229: if ((blockId & Store.BLOCK_MASK) == 0)
230: throw stateError(L.l("Block 0 is reserved."));
231:
232: block = new ReadBlock(store, blockId);
233:
234: if (dirtyBlock != null) {
235: byte[] dirtyBuffer = dirtyBlock.getBuffer();
236:
237: if (dirtyBuffer != null) {
238: System.arraycopy(dirtyBuffer, 0, block.getBuffer(),
239: 0, dirtyBuffer.length);
240: block.validate();
241: }
242:
243: dirtyBlock.free();
244: }
245:
246: // needs to be outside the synchronized since the put
247: // can cause an LRU drop which might lead to a dirty write
248: block = _blockCache.putIfNew(blockId, block);
249: }
250:
251: return block;
252: }
253:
254: /**
255: * Adds a block that's needs to be flushed.
256: */
257: void addLruDirtyWriteBlock(Block block) {
258: synchronized (_writeQueue) {
259: while (_writeQueueMax < _writeQueue.size()) {
260: try {
261: _writeQueue.wait();
262: } catch (InterruptedException e) {
263: }
264: }
265:
266: _writeQueue.add(block);
267:
268: _writeQueue.notifyAll();
269: }
270: }
271:
272: //
273: // management/statistics
274: //
275:
276: /**
277: * The managed name is null
278: */
279: public String getName() {
280: return null;
281: }
282:
283: /**
284: * The managed type is BlockManager
285: */
286: public String getType() {
287: return "BlockManager";
288: }
289:
290: /**
291: * Returns the capacity.
292: */
293: public long getBlockCapacity() {
294: return _blockCache.getCapacity();
295: }
296:
297: /**
298: * Returns the hit count.
299: */
300: public long getHitCountTotal() {
301: return _blockCache.getHitCount();
302: }
303:
304: /**
305: * Returns the miss count.
306: */
307: public long getMissCountTotal() {
308: return _blockCache.getMissCount();
309: }
310:
311: private static IllegalStateException stateError(String msg) {
312: IllegalStateException e = new IllegalStateException(msg);
313: e.fillInStackTrace();
314: log.log(Level.WARNING, e.toString(), e);
315: return e;
316: }
317:
318: class BlockManagerWriter implements Runnable {
319: public void run() {
320: while (true) {
321: try {
322: Block block = null;
323:
324: synchronized (_writeQueue) {
325: loop: while (true) {
326: for (int i = 0; i < _writeQueue.size(); i++) {
327: block = _writeQueue.get(i);
328:
329: if (block.isFree())
330: break loop;
331: else
332: block = null;
333: }
334:
335: if (_writeQueue.size() == 0)
336: _writeQueue.wait();
337: else
338: _writeQueue.wait(10000);
339: }
340: }
341:
342: block.close();
343:
344: synchronized (_writeQueue) {
345: for (int i = 0; i < _writeQueue.size(); i++) {
346: if (block == _writeQueue.get(i)) {
347: _writeQueue.remove(i);
348: break;
349: }
350: }
351:
352: _writeQueue.notifyAll();
353: }
354: } catch (InterruptedException e) {
355: } catch (Throwable e) {
356: log.log(Level.WARNING, e.toString(), e);
357: }
358: }
359: }
360: }
361: }
|