1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.hadoop.hbase.master.cleaner;
19
20 import java.io.IOException;
21 import java.util.Collections;
22 import java.util.LinkedList;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.concurrent.ExecutionException;
26 import java.util.concurrent.ForkJoinPool;
27 import java.util.concurrent.ForkJoinTask;
28 import java.util.concurrent.RecursiveTask;
29 import java.util.concurrent.atomic.AtomicBoolean;
30
31 import org.apache.commons.logging.Log;
32 import org.apache.commons.logging.LogFactory;
33 import org.apache.hadoop.conf.Configuration;
34 import org.apache.hadoop.fs.FileStatus;
35 import org.apache.hadoop.fs.FileSystem;
36 import org.apache.hadoop.fs.Path;
37 import org.apache.hadoop.fs.PathIsNotEmptyDirectoryException;
38 import org.apache.hadoop.hbase.ScheduledChore;
39 import org.apache.hadoop.hbase.Stoppable;
40 import org.apache.hadoop.hbase.classification.InterfaceAudience;
41 import org.apache.hadoop.hbase.conf.ConfigurationObserver;
42 import org.apache.hadoop.hbase.util.FSUtils;
43 import org.apache.hadoop.hbase.util.FileStatusFilter;
44 import org.apache.hadoop.ipc.RemoteException;
45
46 import com.google.common.annotations.VisibleForTesting;
47 import com.google.common.base.Preconditions;
48 import com.google.common.collect.ImmutableSet;
49 import com.google.common.collect.Iterables;
50 import com.google.common.collect.Lists;
51
52
53
54
55
56 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="ST_WRITE_TO_STATIC_FROM_INSTANCE_METHOD",
57 justification="Static pool will be only updated once.")
58 @InterfaceAudience.Private
59 public abstract class CleanerChore<T extends FileCleanerDelegate> extends ScheduledChore
60 implements ConfigurationObserver {
61
62 private static final Log LOG = LogFactory.getLog(CleanerChore.class.getName());
63 private static final int AVAIL_PROCESSORS = Runtime.getRuntime().availableProcessors();
64
65
66
67
68
69
70
71 public static final String CHORE_POOL_SIZE = "hbase.cleaner.scan.dir.concurrent.size";
72 private static final String DEFAULT_CHORE_POOL_SIZE = "0.25";
73
74 private static class DirScanPool {
75 int size;
76 ForkJoinPool pool;
77 int cleanerLatch;
78 AtomicBoolean reconfigNotification;
79
80 DirScanPool(Configuration conf) {
81 String poolSize = conf.get(CHORE_POOL_SIZE, DEFAULT_CHORE_POOL_SIZE);
82 size = calculatePoolSize(poolSize);
83
84
85 size = size == 0 ? calculatePoolSize(DEFAULT_CHORE_POOL_SIZE) : size;
86 pool = new ForkJoinPool(size);
87 LOG.info("Cleaner pool size is " + size);
88 reconfigNotification = new AtomicBoolean(false);
89 cleanerLatch = 0;
90 }
91
92
93
94
95
96 synchronized void markUpdate(Configuration conf) {
97 int newSize = calculatePoolSize(conf.get(CHORE_POOL_SIZE, DEFAULT_CHORE_POOL_SIZE));
98 if (newSize == size) {
99 LOG.trace("Size from configuration is same as previous=" + newSize +
100 " no need to update.");
101 return;
102 }
103 size = newSize;
104
105 reconfigNotification.set(true);
106 }
107
108
109
110
111 synchronized void updatePool(long timeout) {
112 long stopWaitTime = System.currentTimeMillis() + timeout;
113 while (cleanerLatch != 0 && timeout > 0) {
114 try {
115 wait(timeout);
116 timeout = stopWaitTime - System.currentTimeMillis();
117 } catch (InterruptedException ie) {
118 Thread.currentThread().interrupt();
119 break;
120 }
121 }
122 shutDownNow();
123 LOG.info("Update chore's pool size from " + pool.getParallelism() + " to " + size);
124 pool = new ForkJoinPool(size);
125 }
126
127 synchronized void latchCountUp() {
128 cleanerLatch++;
129 }
130
131 synchronized void latchCountDown() {
132 cleanerLatch--;
133 notifyAll();
134 }
135
136 @SuppressWarnings("FutureReturnValueIgnored")
137 synchronized void submit(ForkJoinTask task) {
138 pool.submit(task);
139 }
140
141 synchronized void shutDownNow() {
142 if (pool == null || pool.isShutdown()) {
143 return;
144 }
145 pool.shutdownNow();
146 }
147 }
148
149
150 private static volatile DirScanPool POOL;
151
152 protected final FileSystem fs;
153 private final Path oldFileDir;
154 private final Configuration conf;
155 protected List<T> cleanersChain;
156 protected Map<String, Object> params;
157 private AtomicBoolean enabled = new AtomicBoolean(true);
158
159 public static void initChorePool(Configuration conf) {
160 if (POOL == null) {
161 POOL = new DirScanPool(conf);
162 }
163 }
164
165 public static void shutDownChorePool() {
166 if (POOL != null) {
167 POOL.shutDownNow();
168 POOL = null;
169 }
170 }
171
172 public CleanerChore(String name, final int sleepPeriod, final Stoppable s, Configuration conf,
173 FileSystem fs, Path oldFileDir, String confKey) {
174 this(name, sleepPeriod, s, conf, fs, oldFileDir, confKey, null);
175 }
176
177
178
179
180
181
182
183
184
185
186
187 public CleanerChore(String name, final int sleepPeriod, final Stoppable s, Configuration conf,
188 FileSystem fs, Path oldFileDir, String confKey, Map<String, Object> params) {
189 super(name, s, sleepPeriod);
190
191 Preconditions.checkNotNull(POOL, "Chore's pool isn't initialized, please call"
192 + "CleanerChore.initChorePool(Configuration) before new a cleaner chore.");
193 this.fs = fs;
194 this.oldFileDir = oldFileDir;
195 this.conf = conf;
196 this.params = params;
197 initCleanerChain(confKey);
198 }
199
200
201
202
203
204
205 static int calculatePoolSize(String poolSize) {
206 if (poolSize.matches("[1-9][0-9]*")) {
207
208
209 int size = Math.min(Integer.parseInt(poolSize), AVAIL_PROCESSORS);
210 if (size == AVAIL_PROCESSORS) {
211 LOG.warn("Use full core processors to scan dir, size=" + size);
212 }
213 return size;
214 } else if (poolSize.matches("0.[0-9]+|1.0")) {
215
216
217 int computedThreads = (int) (AVAIL_PROCESSORS * Double.valueOf(poolSize));
218 if (computedThreads < 1) {
219 LOG.debug("Computed " + computedThreads + " threads for CleanerChore, using 1 instead");
220 return 1;
221 }
222 return computedThreads;
223 } else {
224 LOG.error("Unrecognized value: " + poolSize + " for " + CHORE_POOL_SIZE +
225 ", use default config: " + DEFAULT_CHORE_POOL_SIZE + " instead.");
226 return calculatePoolSize(DEFAULT_CHORE_POOL_SIZE);
227 }
228 }
229
230
231
232
233
234
235
236 protected abstract boolean validate(Path file);
237
238
239
240
241
242 private void initCleanerChain(String confKey) {
243 this.cleanersChain = new LinkedList<T>();
244 String[] logCleaners = conf.getStrings(confKey);
245 if (logCleaners != null) {
246 for (String className : logCleaners) {
247 T logCleaner = newFileCleaner(className, conf);
248 if (logCleaner != null) {
249 LOG.debug("initialize cleaner=" + className);
250 this.cleanersChain.add(logCleaner);
251 }
252 }
253 }
254 }
255
256 @Override
257 public void onConfigurationChange(Configuration conf) {
258 POOL.markUpdate(conf);
259 }
260
261
262
263
264
265
266
267
268 private T newFileCleaner(String className, Configuration conf) {
269 try {
270 Class<? extends FileCleanerDelegate> c = Class.forName(className).asSubclass(
271 FileCleanerDelegate.class);
272 @SuppressWarnings("unchecked")
273 T cleaner = (T) c.getDeclaredConstructor().newInstance();
274 cleaner.setConf(conf);
275 cleaner.init(this.params);
276 return cleaner;
277 } catch (Exception e) {
278 LOG.warn("Can NOT create CleanerDelegate: " + className, e);
279
280 return null;
281 }
282 }
283
284 @Override
285 protected void chore() {
286 if (getEnabled()) {
287 try {
288 POOL.latchCountUp();
289 if (runCleaner()) {
290 if (LOG.isTraceEnabled()) {
291 LOG.trace("Cleaned all WALs under " + oldFileDir);
292 }
293 } else {
294 if (LOG.isTraceEnabled()) {
295 LOG.trace("WALs outstanding under " + oldFileDir);
296 }
297 }
298 } finally {
299 POOL.latchCountDown();
300 }
301
302
303 if (POOL.reconfigNotification.compareAndSet(true, false)) {
304
305
306 POOL.updatePool((long) (0.8 * getTimeUnit().toMillis(getPeriod())));
307 }
308 } else {
309 LOG.trace("Cleaner chore disabled! Not cleaning.");
310 }
311 }
312
313 public Boolean runCleaner() {
314 CleanerTask task = new CleanerTask(this.oldFileDir, true);
315 POOL.submit(task);
316 return task.join();
317 }
318
319
320
321
322
323
324
325 private boolean checkAndDeleteFiles(List<FileStatus> files) {
326 if (files == null) {
327 return true;
328 }
329
330
331 List<FileStatus> validFiles = Lists.newArrayListWithCapacity(files.size());
332 List<FileStatus> invalidFiles = Lists.newArrayList();
333 for (FileStatus file : files) {
334 if (validate(file.getPath())) {
335 validFiles.add(file);
336 } else {
337 LOG.warn("Found a wrongly formatted file: " + file.getPath() + " - will delete it.");
338 invalidFiles.add(file);
339 }
340 }
341
342 Iterable<FileStatus> deletableValidFiles = validFiles;
343
344 for (T cleaner : cleanersChain) {
345 if (cleaner.isStopped() || getStopper().isStopped()) {
346 LOG.warn("A file cleaner" + this.getName() + " is stopped, won't delete any more files in:"
347 + this.oldFileDir);
348 return false;
349 }
350
351 Iterable<FileStatus> filteredFiles = cleaner.getDeletableFiles(deletableValidFiles);
352
353
354 if (LOG.isTraceEnabled()) {
355 ImmutableSet<FileStatus> filteredFileSet = ImmutableSet.copyOf(filteredFiles);
356 for (FileStatus file : deletableValidFiles) {
357 if (!filteredFileSet.contains(file)) {
358 LOG.trace(file.getPath() + " is not deletable according to:" + cleaner);
359 }
360 }
361 }
362
363 deletableValidFiles = filteredFiles;
364 }
365
366 Iterable<FileStatus> filesToDelete = Iterables.concat(invalidFiles, deletableValidFiles);
367 return deleteFiles(filesToDelete) == files.size();
368 }
369
370
371
372
373
374
375 protected int deleteFiles(Iterable<FileStatus> filesToDelete) {
376 int deletedFileCount = 0;
377 for (FileStatus file : filesToDelete) {
378 Path filePath = file.getPath();
379 LOG.trace("Removing " + file + " from archive");
380 try {
381 boolean success = this.fs.delete(filePath, false);
382 if (success) {
383 deletedFileCount++;
384 } else {
385 LOG.warn("Attempted to delete:" + filePath
386 + ", but couldn't. Run cleaner chain and attempt to delete on next pass.");
387 }
388 } catch (IOException e) {
389 e = e instanceof RemoteException ?
390 ((RemoteException)e).unwrapRemoteException() : e;
391 LOG.warn("Error while deleting: " + filePath, e);
392 }
393 }
394 return deletedFileCount;
395 }
396
397 @Override
398 public synchronized void cleanup() {
399 for (T lc : this.cleanersChain) {
400 try {
401 lc.stop("Exiting");
402 } catch (Throwable t) {
403 LOG.warn("Stopping", t);
404 }
405 }
406 }
407
408 @VisibleForTesting
409 int getChorePoolSize() {
410 return POOL.size;
411 }
412
413
414
415
416 public boolean setEnabled(final boolean enabled) {
417 return this.enabled.getAndSet(enabled);
418 }
419
420 public boolean getEnabled() {
421 return this.enabled.get();
422 }
423
424 private interface Action<T> {
425 T act() throws IOException;
426 }
427
428
429
430
431
432 private class CleanerTask extends RecursiveTask<Boolean> {
433 private final Path dir;
434 private final boolean root;
435
436 CleanerTask(final FileStatus dir, final boolean root) {
437 this(dir.getPath(), root);
438 }
439
440 CleanerTask(final Path dir, final boolean root) {
441 this.dir = dir;
442 this.root = root;
443 }
444
445 @Override
446 protected Boolean compute() {
447 LOG.trace("Cleaning under " + dir);
448 List<FileStatus> subDirs;
449 List<FileStatus> tmpFiles;
450 final List<FileStatus> files;
451 try {
452
453
454 subDirs = FSUtils.listStatusWithStatusFilter(fs, dir, new FileStatusFilter() {
455 @Override
456 public boolean accept(FileStatus f) {
457 return f.isDirectory();
458 }
459 });
460 if (subDirs == null) {
461 subDirs = Collections.emptyList();
462 }
463 tmpFiles = FSUtils.listStatusWithStatusFilter(fs, dir, new FileStatusFilter() {
464 @Override
465 public boolean accept(FileStatus f) {
466 return f.isFile();
467 }
468 });
469 files = tmpFiles == null ? Collections.<FileStatus>emptyList() : tmpFiles;
470 } catch (IOException ioe) {
471 LOG.warn("failed to get FileStatus for contents of '" + dir + "'", ioe);
472 return false;
473 }
474
475 boolean allFilesDeleted = true;
476 if (!files.isEmpty()) {
477 allFilesDeleted = deleteAction(new Action<Boolean>() {
478 @Override
479 public Boolean act() throws IOException {
480 return checkAndDeleteFiles(files);
481 }
482 }, "files");
483 }
484
485 boolean allSubdirsDeleted = true;
486 if (!subDirs.isEmpty()) {
487 final List<CleanerTask> tasks = Lists.newArrayListWithCapacity(subDirs.size());
488 for (FileStatus subdir : subDirs) {
489 CleanerTask task = new CleanerTask(subdir, false);
490 tasks.add(task);
491 task.fork();
492 }
493 allSubdirsDeleted = deleteAction(new Action<Boolean>() {
494 @Override
495 public Boolean act() throws IOException {
496 return getCleanResult(tasks);
497 }
498 }, "subdirs");
499 }
500
501 boolean result = allFilesDeleted && allSubdirsDeleted;
502
503
504 if (result && !root) {
505 result &= deleteAction(new Action<Boolean>() {
506 @Override
507 public Boolean act() throws IOException {
508 return fs.delete(dir, false);
509 }
510 }, "dir");
511 }
512 return result;
513 }
514
515
516
517
518
519
520
521 private boolean deleteAction(Action<Boolean> deletion, String type) {
522 boolean deleted;
523 try {
524 LOG.trace("Start deleting " + type + " under " + dir);
525 deleted = deletion.act();
526 } catch (PathIsNotEmptyDirectoryException exception) {
527
528
529
530 LOG.debug("Couldn't delete '" + dir + "' yet because it isn't empty. Probably transient. " +
531 "exception details at TRACE.");
532 LOG.trace("Couldn't delete '" + dir + "' yet because it isn't empty w/exception.",
533 exception);
534 deleted = false;
535 } catch (IOException ioe) {
536 LOG.info("Could not delete " + type + " under " + dir + ". might be transient; we'll " +
537 "retry. if it keeps happening, use following exception when asking on mailing list.",
538 ioe);
539 deleted = false;
540 }
541 LOG.trace("Finish deleting " + type + " under " + dir + " deleted=" + deleted);
542 return deleted;
543 }
544
545
546
547
548
549
550
551 private boolean getCleanResult(List<CleanerTask> tasks) throws IOException {
552 boolean cleaned = true;
553 try {
554 for (CleanerTask task : tasks) {
555 cleaned &= task.get();
556 }
557 } catch (InterruptedException | ExecutionException e) {
558 throw new IOException(e);
559 }
560 return cleaned;
561 }
562 }
563 }