1 /***
2 * Copyright 2003-2010 Terracotta, Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 package net.sf.ehcache.terracotta;
18
19 import net.sf.ehcache.CacheException;
20 import net.sf.ehcache.Disposable;
21 import net.sf.ehcache.Ehcache;
22 import net.sf.ehcache.Status;
23 import net.sf.ehcache.distribution.RemoteCacheException;
24 import net.sf.ehcache.store.MemoryLimitedCacheLoader;
25 import org.slf4j.Logger;
26 import org.slf4j.LoggerFactory;
27
28 import java.io.IOException;
29 import java.util.Set;
30
31 /***
32 * A {@link net.sf.ehcache.bootstrap.BootstrapCacheLoader} that will load Elements into a Terracotta clustered cache, based on a previously
33 * snapshotted key set. It is also responsible to create snapshot files to disk
34 * @author Alex Snaps
35 */
36 public class TerracottaBootstrapCacheLoader extends MemoryLimitedCacheLoader implements Disposable {
37
38 /***
39 * The default interval in seconds, between each snapshot
40 */
41 public static final long DEFAULT_INTERVAL = 10 * 60;
42 /***
43 * The default on whether to do the snapshot on a dedicated thread or using the CacheManager's
44 * {@link java.util.concurrent.ScheduledExecutorService}
45 */
46 public static final boolean DEFAULT_DEDICATED_THREAD = false;
47
48 private static final Logger LOG = LoggerFactory.getLogger(TerracottaBootstrapCacheLoader.class);
49
50 private final boolean aSynchronous;
51 private final boolean doKeySnapshot;
52 private final boolean doKeySnapshotOnDedicatedThread;
53 private final long interval;
54 private final String directory;
55
56 private volatile KeySnapshotter keySnapshotter;
57 private volatile boolean immediateShutdown;
58 private volatile boolean doKeySnapshotOnDispose;
59
60 private TerracottaBootstrapCacheLoader(final boolean doKeySnapshot, final boolean aSynchronous,
61 final String directory, final long interval, final boolean doKeySnapshotOnDedicatedThread) {
62 this.aSynchronous = aSynchronous;
63 this.doKeySnapshot = doKeySnapshot;
64 this.doKeySnapshotOnDedicatedThread = doKeySnapshotOnDedicatedThread;
65 this.interval = interval;
66 this.directory = directory;
67 }
68
69 /***
70 * Constructor
71 * @param asynchronous do the loading asynchronously, or synchronously
72 * @param directory the directory to read snapshot files from, and write them to
73 * @param doKeySnapshots Whether to do keysnapshotting
74 */
75 public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, boolean doKeySnapshots) {
76 this(doKeySnapshots, asynchronous, directory, DEFAULT_INTERVAL, DEFAULT_DEDICATED_THREAD);
77 }
78
79 /***
80 * Constructor
81 * @param asynchronous do the loading asynchronously, or synchronously
82 * @param directory the directory to read snapshot files from, and write them to
83 * @param interval the interval in seconds at which the snapshots of the local key set has to occur
84 */
85 public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, long interval) {
86 this(asynchronous, directory, interval, false);
87 }
88
89 /***
90 * Constructor
91 * @param asynchronous do the loading asynchronously, or synchronously
92 * @param directory the directory to read snapshot files from, and write them to
93 * @param interval the interval in seconds at which the snapshots of the local key set has to occur
94 * @param onDedicatedThread whether to do the snapshot on a dedicated thread or using the CacheManager's
95 * {@link java.util.concurrent.ScheduledExecutorService ScheduledExecutorService}
96 */
97 public TerracottaBootstrapCacheLoader(final boolean asynchronous, String directory, long interval, boolean onDedicatedThread) {
98 this(true, asynchronous, directory, interval, onDedicatedThread);
99 }
100
101 /***
102 * Whether the on going keysnapshot will finish before the instance is disposed
103 * @return true if disposable is immediate
104 * @see Disposable
105 */
106 public boolean isImmediateShutdown() {
107 return immediateShutdown;
108 }
109
110 /***
111 * Sets whether the disposal of the instance will let the potential current key set being written to disk finish, or whether the
112 * shutdown will be immediate
113 * @param immediateShutdown true if immediate, false to let the snapshot finish
114 */
115 public void setImmediateShutdown(final boolean immediateShutdown) {
116 this.immediateShutdown = immediateShutdown;
117 }
118
119 /***
120 * {@inheritDoc}
121 */
122 public void load(final Ehcache cache) throws CacheException {
123 if (!cache.getCacheConfiguration().isTerracottaClustered()) {
124 LOG.error("You're trying to bootstrap a non Terracotta clustered cache with a TerracottaBootstrapCacheLoader! Cache " +
125 "'{}' will not be bootstrapped and no keySet snapshot will be recorded...", cache.getName());
126 return;
127 }
128
129 if (cache.getStatus() != Status.STATUS_ALIVE) {
130 throw new CacheException("Cache '" + cache.getName() + "' isn't alive yet: " + cache.getStatus());
131 }
132
133 if (isAsynchronous()) {
134 BootstrapThread thread = new BootstrapThread(cache);
135 thread.start();
136 } else {
137 doLoad(cache);
138 }
139 }
140
141 private void doLoad(final Ehcache cache) {
142 final RotatingSnapshotFile snapshotFile = new RotatingSnapshotFile(directory == null ? cache.getCacheManager()
143 .getDiskStorePath() : directory, cache.getName());
144 try {
145 final Set<Object> keys = snapshotFile.readAll();
146 int loaded = 0;
147 for (Object key : keys) {
148 if (isInMemoryLimitReached(cache, loaded)) {
149 break;
150 }
151 cache.get(key);
152 loaded++;
153 }
154 LOG.info("Finished loading {} keys (of {} on disk) from previous snapshot for Cache '{}'",
155 new Object[] {Integer.valueOf(loaded), keys.size(), cache.getName()});
156 } catch (IOException e) {
157 LOG.error("Couldn't load keySet for Cache '{}'", cache.getName(), e);
158 }
159
160 if (doKeySnapshot) {
161 keySnapshotter = new KeySnapshotter(cache, interval, doKeySnapshotOnDedicatedThread, snapshotFile);
162 }
163 }
164
165 /***
166 * {@inheritDoc}
167 */
168 public boolean isAsynchronous() {
169 return aSynchronous;
170 }
171
172 /***
173 * Will shut the keysnapshot thread and other resources down.
174 * If a snapshot is currently in progress, the method will either shutdown immediately or let the snapshot finish
175 * based on the configured {@link #setImmediateShutdown(boolean)} value
176 */
177 public void dispose() {
178 if (keySnapshotter != null) {
179 if (doKeySnapshotOnDispose) {
180 try {
181 keySnapshotter.doSnapshot();
182 } catch (IOException e) {
183 LOG.error("Error writing local key set for Cache '{}'", keySnapshotter.getCacheName(), e);
184 }
185 } else {
186 keySnapshotter.dispose(immediateShutdown);
187 }
188 }
189 }
190
191 /***
192 * Calling this method will result in a snapshot being taken or wait for the one in progress to finish
193 * @throws IOException On exception being thrown while doing the snapshot
194 */
195 public void doLocalKeySnapshot() throws IOException {
196 keySnapshotter.doSnapshot();
197 }
198
199 /***
200 * {@inheritDoc}
201 */
202 @Override
203 public Object clone() throws CloneNotSupportedException {
204 return super.clone();
205 }
206
207 /***
208 * Accessor to the associated {@link KeySnapshotter}
209 * @return the {@link KeySnapshotter} used by this loader instance
210 */
211 KeySnapshotter getKeySnapshotter() {
212 return keySnapshotter;
213 }
214
215 /***
216 * Configures the Loader to take a snapshot when it is being disposed
217 * @param doKeySnapshotOnDispose whether to snapshot on loader disposal
218 */
219 public void setSnapshotOnDispose(final boolean doKeySnapshotOnDispose) {
220 this.doKeySnapshotOnDispose = doKeySnapshotOnDispose;
221 }
222
223 /***
224 * A background daemon thread that asynchronously calls doLoad
225 */
226 private final class BootstrapThread extends Thread {
227 private Ehcache cache;
228
229 public BootstrapThread(Ehcache cache) {
230 super("Bootstrap Thread for cache " + cache.getName());
231 this.cache = cache;
232 setDaemon(true);
233 setPriority(Thread.NORM_PRIORITY);
234 }
235
236 /***
237 * RemoteDebugger thread method.
238 */
239 public final void run() {
240 try {
241 doLoad(cache);
242 } catch (RemoteCacheException e) {
243 LOG.warn("Error asynchronously performing bootstrap. The cause was: " + e.getMessage(), e);
244 }
245 cache = null;
246 }
247 }
248 }