1 /*-
2  * See the file LICENSE for redistribution information.
3  *
4  * Copyright (c) 2002, 2014 Oracle and/or its affiliates.  All rights reserved.
5  *
6  */
7 
8 package com.sleepycat.je.log;
9 
10 import java.io.File;
11 import java.io.RandomAccessFile;
12 
13 import com.sleepycat.je.EnvironmentConfig;
14 import com.sleepycat.je.dbi.EnvironmentImpl;
15 import com.sleepycat.je.utilint.DbLsn;
16 import com.sleepycat.je.utilint.LoggerUtils;
17 
18 /**
19  * Warm-up the file system cache during startup, for some portion of the log
20  * that is not being read by recovery.
21  *
22  * This functionality is documented for the most part by {@link
23  * EnvironmentConfig#LOG_FILE_WARM_UP_SIZE}.
24  *
25  * One thing not mentioned is that cleaner log deletion is disabled during the
26  * warm-up. This is necessary to avoid dealing with the possibility of files
27  * being deleted while being read for warm-up. Although it is extremely likely
28  * that warm-up will finish before a cleaner-checkpoint cycle, we disable file
29  * deletions simply to avoid having to code for this remote possibility. This
30  * isn't documented because file deletions will probably never be prevented by
31  * warm-up, documenting this would only cause unnecessary confusion.
32  */
33 public class FileCacheWarmer extends Thread {
34 
35     private final EnvironmentImpl envImpl;
36     private final long recoveryStartLsn;
37     private final long endOfLogLsn;
38     private final int warmUpSize;
39     private final int bufSize;
40     private volatile boolean stop;
41 
FileCacheWarmer(final EnvironmentImpl envImpl, final long recoveryStartLsn, final long endOfLogLsn, final int warmUpSize, final int bufSize)42     FileCacheWarmer(final EnvironmentImpl envImpl,
43                     final long recoveryStartLsn,
44                     final long endOfLogLsn,
45                     final int warmUpSize,
46                     final int bufSize) {
47         this.envImpl = envImpl;
48         this.recoveryStartLsn = recoveryStartLsn;
49         this.endOfLogLsn = endOfLogLsn;
50         this.warmUpSize = warmUpSize;
51         this.bufSize = bufSize;
52         stop = false;
53     }
54 
55     /**
56      * Stops this thread. At most one read will occur after calling this
57      * method, and then the thread will exit.
58      */
shutdown()59     void shutdown() {
60         stop = true;
61     }
62 
63     @Override
run()64     public void run() {
65         try {
66             doRun();
67         } catch (Throwable e) {
68 
69             /*
70              * Log error as SEVERE but do not invalidate environment since it
71              * perfectly usable.
72              */
73             LoggerUtils.traceAndLogException(
74                 envImpl, FileCacheWarmer.class.getName(), "run",
75                 "Unable to warm file system cache due to exception", e);
76 
77         } finally {
78             /* Ensure that this thread can be GC'd after it stops. */
79             envImpl.getFileManager().clearFileCacheWarmer();
80         }
81     }
82 
doRun()83     private void doRun()
84         throws Throwable {
85 
86         final FileManager fm = envImpl.getFileManager();
87 
88         final long ONE_MB = 1L << 20;
89 
90         long remaining = (warmUpSize * ONE_MB) -
91             DbLsn.getTrueDistance(recoveryStartLsn, endOfLogLsn, fm);
92 
93         if (remaining <= 0) {
94             return;
95         }
96 
97         // System.out.println("FileCacheWarmer start " + remaining);
98 
99         final byte[] buf = new byte[bufSize];
100 
101         long fileNum = DbLsn.getFileNumber(recoveryStartLsn);
102         long fileOff = DbLsn.getFileOffset(recoveryStartLsn);
103 
104         String filePath = fm.getFullFileName(fileNum);
105         File file = new File(filePath);
106         RandomAccessFile raf = null;
107 
108         envImpl.getCleaner().addProtectedFileRange(0L);
109         try {
110             raf = new RandomAccessFile(file, "r");
111 
112             while (!stop && remaining > 0) {
113 
114                 if (fileOff <= 0) {
115                     raf.close();
116                     raf = null;
117 
118                     final Long nextFileNum = fm.getFollowingFileNum(
119                         fileNum, false /*forward*/);
120 
121                     if (nextFileNum == null) {
122                         throw new RuntimeException(
123                             "No file preceding " + fileNum);
124                     }
125 
126                     fileNum = nextFileNum;
127                     filePath = fm.getFullFileName(fileNum);
128                     file = new File(filePath);
129                     raf = new RandomAccessFile(file, "r");
130                     fileOff = raf.length();
131                 }
132 
133                 final long pos = Math.max(0L, fileOff - bufSize);
134                 raf.seek(pos);
135 
136                 final int bytes = (int) (fileOff - pos);
137                 final int read = raf.read(buf, 0, bytes);
138 
139                 if (read != bytes) {
140                     throw new IllegalStateException(
141                         "Requested " + bytes + " bytes but read " + read);
142                 }
143 
144                 remaining -= bytes;
145                 fileOff = pos;
146             }
147 
148             raf.close();
149             raf = null;
150 
151         } finally {
152 
153             // System.out.println(
154             // "FileCacheWarmer finish " + remaining + " " + stop);
155 
156             envImpl.getCleaner().removeProtectedFileRange(0L);
157 
158             if (raf != null) {
159                 try {
160                     raf.close();
161                 } catch (Exception e) {
162                     /* Ignore this. Another exception is in flight. */
163                 }
164             }
165         }
166     }
167 }
168