KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sleepycat > je > log > FSyncManager


1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002,2006 Oracle. All rights reserved.
5  *
6  * $Id: FSyncManager.java,v 1.18 2006/10/30 21:14:20 bostic Exp $
7  */

8
9 package com.sleepycat.je.log;
10
11 import com.sleepycat.je.DatabaseException;
12 import com.sleepycat.je.EnvironmentStats;
13 import com.sleepycat.je.RunRecoveryException;
14 import com.sleepycat.je.StatsConfig;
15 import com.sleepycat.je.config.EnvironmentParams;
16 import com.sleepycat.je.dbi.EnvironmentImpl;
17 import com.sleepycat.je.latch.Latch;
18 import com.sleepycat.je.latch.LatchSupport;
19 import com.sleepycat.je.utilint.PropUtil;
20
21 /*
22  * The FsyncManager ensures that only one file fsync is issued at a time, for
23  * performance optimization. The goal is to reduce the number of fsyncs issued
24  * by the system by issuing 1 fsync on behalf of a number of threads.
25  *
26  * For example, suppose these writes happen which all need to be fsynced to
27  * disk:
28  *
29  * thread 1 writes a commit record
30  * thread 2 writes a checkpoint
31  * thread 3 writes a commit record
32  * thread 4 writes a commit record
33  * thread 5 writes a checkpoint
34  *
35  * Rather than executing 5 fsyncs, which all must happen synchronously, we hope
36  * to issue fewer. How many fewer depend on timing. Note that the writes
37  * themselves are serialized and are guaranteed to run in order.
38  *
39  * For example:
40  * thread 1 wants to fsync first, no other fsync going on, will issue fsync
41  * thread 2 waits
42  * thread 3 waits
43  * thread 4 waits
44  * - before thread 5 comes, thread 1 finishes fsyncing and returns to
45  * the caller. Now another fsync can be issued that will cover threads
46  * 2,3,4. One of those threads (2, 3, 4} issues the fsync, the others
47  * block.
48  * thread 5 wants to fsync, but sees one going on, so will wait.
49  * - the fsync issued for 2,3,4 can't cover thread 5 because we're not sure
50  * if thread 5's write finished before that fsync call. Thread 5 will have
51  * to issue its own fsync.
52  *
53  * Target file
54  * -----------
55  * Note that when the buffer pool starts a new file, we fsync the previous file
56  * under the log write latch. Therefore, at any time we only have one target
57  * file to fsync, which is the current write buffer. We do this so that we
58  * don't have to coordinate between files. For example, suppose log files have
59  * 1000 bytes and a commit record is 10 bytes. An LSN of value 6/990 is in
60  * file 6 at offset 990.
61  *
62  * thread 1: logWriteLatch.acquire()
63  * write commit record to LSN 6/980
64  * logWriteLatch.release()
65  * thread 2: logWriteLatch.acquire()
66  * write commit record to LSN 6/990
67  * logWriteLatch.release
68  * thread 3: logWriteLatch.acquire()
69  * gets 7/000 as the next LSN to use
70  * see that we flipped to a new file, so call fsync on file 6
71  * write commit record to LSN 7/000
72  * logWriteLatch.release()
73  *
74  * Thread 3 will fsync file 6 within the log write latch. That way, at any
75  * time, any non-latched fsyncs should only fsync the latest file. If we
76  * didn't do, there's the chance that thread 3 would fsync file 7 and return to
77  * its caller before the thread 1 and 2 got an fsync for file 6. That wouldn't
78  * be correct, because thread 3's commit might depend on file 6.
79  *
80  * Note that the FileManager keeps a file descriptor that corresponds to the
81  * current end of file, and that is what we fsync.
82  */

83 class FSyncManager {
84     private EnvironmentImpl envImpl;
85     private long timeout;
86
87     /* Use as the target for a synchronization block. */
88     private Latch fsyncLatch;
89                               
90     private volatile boolean fsyncInProgress;
91     private FSyncGroup nextFSyncWaiters;
92
93     /* stats */
94     private long nFSyncRequests = 0;
95     private long nFSyncs = 0;
96     private long nTimeouts = 0;
97
98     FSyncManager(EnvironmentImpl envImpl)
99         throws DatabaseException {
100         timeout = PropUtil.microsToMillis(envImpl.getConfigManager().getLong(
101             EnvironmentParams.LOG_FSYNC_TIMEOUT));
102         this.envImpl = envImpl;
103
104         fsyncLatch = LatchSupport.makeLatch("fsyncLatch", envImpl);
105         fsyncInProgress = false;
106         nextFSyncWaiters = new FSyncGroup(timeout, envImpl);
107     }
108
109     /**
110      * Request that this file be fsynced to disk. This thread may or may not
111      * actually execute the fsync, but will not return until a fsync has been
112      * issued and executed on behalf of its write. There is a timeout period
113      * specified by EnvironmentParam.LOG_FSYNC_TIMEOUT that ensures that no
114      * thread gets stuck here indefinitely.
115      *
116      * When a thread comes in, it will find one of two things.
117      * 1. There is no fsync going on right now. This thread should go
118      * ahead and fsync.
119      * 2. There is an active fsync, wait until it's over before
120      * starting a new fsync.
121      *
122      * When a fsync is going on, all those threads that come along are grouped
123      * together as the nextFsyncWaiters. When the current fsync is finished,
124      * one of those nextFsyncWaiters will be selected as a leader to issue the
125      * next fsync. The other members of the group will merely wait until the
126      * fsync done on their behalf is finished.
127      *
128      * When a thread finishes a fsync, it has to:
129      * 1. wake up all the threads that were waiting for its fsync call.
130      * 2. wake up one member of the next group of waiting threads (the
131      * nextFsyncWaiters) so that thread can become the new leader
132      * and issue the next fysnc call.
133      *
134      * If a non-leader member of the nextFsyncWaiters times out, it will issue
135      * its own fsync anyway, in case something happened to the leader.
136      */

137     void fsync()
138         throws DatabaseException {
139
140         boolean doFsync = false;
141         boolean isLeader = false;
142         boolean needToWait = false;
143         FSyncGroup inProgressGroup = null;
144         FSyncGroup myGroup = null;
145
146         synchronized (fsyncLatch) {
147             nFSyncRequests++;
148
149             /* Figure out if we're calling fsync or waiting. */
150             if (fsyncInProgress) {
151                 needToWait = true;
152                 myGroup = nextFSyncWaiters;
153             } else {
154                 isLeader = true;
155                 doFsync = true;
156                 fsyncInProgress = true;
157                 inProgressGroup = nextFSyncWaiters;
158                 nextFSyncWaiters = new FSyncGroup(timeout, envImpl);
159             }
160         }
161
162         if (needToWait) {
163
164             /*
165              * Note that there's no problem if we miss the notify on this set
166              * of waiters. We can check state in the FSyncGroup before we begin
167              * to wait.
168              *
169              * All members of the group may return from their waitForFSync()
170              * call with the need to do a fsync, because of timeout. Only one
171              * will return as the leader.
172              */

173             int waitStatus = myGroup.waitForFsync();
174             
175             if (waitStatus == FSyncGroup.DO_LEADER_FSYNC) {
176                 synchronized (fsyncLatch) {
177
178                     /*
179                      * Check if there's a fsync in progress; this might happen
180                      * even if you were designated the leader if a new thread
181                      * came in between the point when the old leader woke you
182                      * up and now. This new thread may have found that there
183                      * was no fsync in progress, and may have started a fsync.
184                      */

185                     if (!fsyncInProgress) {
186                         isLeader = true;
187                         doFsync = true;
188                         fsyncInProgress = true;
189                         inProgressGroup = myGroup;
190                         nextFSyncWaiters = new FSyncGroup(timeout, envImpl);
191                     }
192                 }
193             } else if (waitStatus == FSyncGroup.DO_TIMEOUT_FSYNC) {
194                 doFsync = true;
195                 synchronized (fsyncLatch) {
196                     nTimeouts++;
197                 }
198             }
199         }
200
201         if (doFsync) {
202
203             /*
204              * There are 3 ways that this fsync gets called:
205              *
206              * 1. A thread calls sync and there is not a sync call already in
207              * progress. That thread executes fsync for itself only. Other
208              * threads requesting sync form a group of waiters.
209              *
210              * 2. A sync finishes and wakes up a group of waiters. The first
211              * waiter in the group to wake up becomes the leader. It executes
212              * sync for it's group of waiters. As above, other threads
213              * requesting sync form a new group of waiters.
214              *
215              * 3. If members of a group of waiters have timed out, they'll all
216              * just go and do their own sync for themselves.
217              */

218             executeFSync();
219
220             synchronized (fsyncLatch) {
221                 nFSyncs++;
222                 if (isLeader) {
223
224                     /*
225                      * Wake up the group that requested the fsync before you
226                      * started. They've piggybacked off your fsync.
227                      */

228                     inProgressGroup.wakeupAll();
229
230                     /*
231                      * Wake up a single waiter, who will become the next
232                      * leader.
233                      */

234                     nextFSyncWaiters.wakeupOne();
235                     fsyncInProgress = false;
236                 }
237             }
238         }
239     }
240
241     /*
242      * Stats.
243      */

244     long getNFSyncRequests() {
245         return nFSyncRequests;
246     }
247
248     long getNFSyncs() {
249         return nFSyncs;
250     }
251
252     long getNTimeouts() {
253         return nTimeouts;
254     }
255
256     void loadStats(StatsConfig config, EnvironmentStats stats)
257         throws DatabaseException {
258
259         stats.setNFSyncs(nFSyncs);
260         stats.setNFSyncRequests(nFSyncRequests);
261         stats.setNFSyncTimeouts(nTimeouts);
262
263         if (config.getClear()) {
264             nFSyncs = 0;
265             nFSyncRequests = 0;
266             nTimeouts = 0;
267         }
268     }
269
270     /**
271      * Put the fsync execution into this method so it can be overridden for
272      * testing purposes.
273      */

274     protected void executeFSync()
275         throws DatabaseException {
276
277         envImpl.getFileManager().syncLogEnd();
278     }
279
280     /*
281      * Embodies a group of threads waiting for a common fsync. Note that
282      * there's no collection here; group membership is merely that the threads
283      * are all waiting on the same monitor.
284      */

285     static class FSyncGroup {
286         static int DO_TIMEOUT_FSYNC = 0;
287         static int DO_LEADER_FSYNC = 1;
288         static int NO_FSYNC_NEEDED = 2;
289
290         private volatile boolean fsyncDone;
291         private long fsyncTimeout;
292         private boolean leaderExists;
293         private EnvironmentImpl envImpl;
294
295         FSyncGroup(long fsyncTimeout, EnvironmentImpl envImpl) {
296             this.fsyncTimeout = fsyncTimeout;
297             fsyncDone = false;
298             leaderExists = false;
299             this.envImpl = envImpl;
300         }
301
302         synchronized boolean getLeader() {
303             if (fsyncDone) {
304                 return false;
305             } else {
306                 if (leaderExists) {
307                     return false;
308                 } else {
309                     leaderExists = true;
310                     return true;
311                 }
312             }
313         }
314
315         /**
316          * Wait for either a turn to execute a fsync, or to find out that a
317          * fsync was done on your behalf.
318      *
319          * @return true if the fsync wasn't done, and this thread needs to
320          * execute a fsync when it wakes up. This may be true because it's the
321          * leader of its group, or because the wait timed out.
322          */

323         synchronized int waitForFsync()
324             throws RunRecoveryException {
325
326             int status = 0;
327
328             if (!fsyncDone) {
329                 long startTime = System.currentTimeMillis();
330                 while (true) {
331
332                     try {
333                         wait(fsyncTimeout);
334                     } catch (InterruptedException JavaDoc e) {
335                         throw new RunRecoveryException(envImpl,
336                            "Unexpected interrupt while waiting for fsync", e);
337                     }
338
339                     /*
340                      * This thread was awoken either by a timeout, by a notify,
341                      * or by an interrupt. Is the fsync done?
342                      */

343                     if (fsyncDone) {
344                         /* The fsync we're waiting on is done, leave. */
345                         status = NO_FSYNC_NEEDED;
346                         break;
347                     } else {
348
349                         /*
350                          * The fsync is not done -- were we woken up to become
351                          * the leader?
352                          */

353                         if (!leaderExists) {
354                             leaderExists = true;
355                             status = DO_LEADER_FSYNC;
356                             break;
357                         } else {
358
359                             /*
360                              * We're just a waiter. See if we're timed out or
361                              * have more to wait.
362                              */

363                             long now = System.currentTimeMillis();
364                             if ((now - startTime) > fsyncTimeout) {
365                                 /* we timed out. */
366                                 status = DO_TIMEOUT_FSYNC;
367                                 break;
368                             }
369                         }
370                     }
371                 }
372             }
373
374             return status;
375         }
376
377         synchronized void wakeupAll() {
378             fsyncDone = true;
379             notifyAll();
380         }
381
382         synchronized void wakeupOne() {
383         /* FindBugs whines here. */
384             notify();
385         }
386     }
387 }
388
Popular Tags