From 32981ded8977add5e52f0e836e7d3de0c8bbfd4d Mon Sep 17 00:00:00 2001 From: Gary Russell Date: Tue, 15 Dec 2015 15:48:50 -0500 Subject: [PATCH] INT-3915: Possible Memory Leak in FileChannelCache JIRA: https://jira.spring.io/browse/INT-3915 Close the redundant `FileChannel` when Map collision occurs. (cherry picked from commit b5ec73b) --- .../file/locking/FileChannelCache.java | 103 ++++++++++-------- 1 file changed, 58 insertions(+), 45 deletions(-) diff --git a/spring-integration-file/src/main/java/org/springframework/integration/file/locking/FileChannelCache.java b/spring-integration-file/src/main/java/org/springframework/integration/file/locking/FileChannelCache.java index 45c349b639..7397bac068 100644 --- a/spring-integration-file/src/main/java/org/springframework/integration/file/locking/FileChannelCache.java +++ b/spring-integration-file/src/main/java/org/springframework/integration/file/locking/FileChannelCache.java @@ -1,11 +1,11 @@ /* - * Copyright 2002-2013 the original author or authors. + * Copyright 2002-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,9 +19,9 @@ package org.springframework.integration.file.locking; import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import java.nio.channels.FileChannel; import java.nio.channels.FileLock; import java.nio.channels.OverlappingFileLockException; -import java.nio.channels.FileChannel; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -29,59 +29,72 @@ import java.util.concurrent.ConcurrentMap; * Static cache of FileLocks that can be used to ensure that only a single lock is used inside this ClassLoader. * * @author Iwein Fuld + * @author Gary Russell * @since 2.0 */ final class FileChannelCache { - private static ConcurrentMap channelCache = new ConcurrentHashMap(); + private static ConcurrentMap channelCache = new ConcurrentHashMap(); - /** - * Try to get a lock for this file while guaranteeing that the same channel will be used for all file locks in this - * VM. If the lock could not be acquired this method will return null. - *

- * Locks acquired through this method should be passed back to #closeChannelFor to prevent memory leaks. - *

- * Thread safe. - */ - public static FileLock tryLockFor(File fileToLock) throws IOException { - FileChannel channel = channelCache.get(fileToLock); - if (channel == null) { - FileChannel newChannel = new RandomAccessFile(fileToLock, "rw").getChannel(); - FileChannel original = channelCache.putIfAbsent(fileToLock, newChannel); - channel = (original != null) ? original : newChannel; - } - FileLock lock = null; - if (channel != null) { - try { - lock = channel.tryLock(); - } - catch (OverlappingFileLockException e) { - // File is already locked in this thread or virtual machine - } - } - return lock; - } + /** + * Try to get a lock for this file while guaranteeing that the same channel will be used for all file locks in this + * VM. If the lock could not be acquired this method will return null. + *

+ * Locks acquired through this method should be passed back to #closeChannelFor to prevent memory leaks. + *

+ * Thread safe. + */ + public static FileLock tryLockFor(File fileToLock) throws IOException { + FileChannel channel = channelCache.get(fileToLock); + if (channel == null) { + @SuppressWarnings("resource") + FileChannel newChannel = new RandomAccessFile(fileToLock, "rw").getChannel(); + FileChannel original = channelCache.putIfAbsent(fileToLock, newChannel); + if (original != null) { + channel = original; + try { + newChannel.close(); + } + catch (IOException e) { + // ignore + } + } + else { + channel = newChannel; + } + } + FileLock lock = null; + if (channel != null) { + try { + lock = channel.tryLock(); + } + catch (OverlappingFileLockException e) { + // File is already locked in this thread or virtual machine + } + } + return lock; + } - /** - * Close the channel for the file passed in. - *

- * Thread safe. - */ - public static void closeChannelFor(File fileToUnlock) { - FileChannel fileChannel = channelCache.remove(fileToUnlock); - if (fileChannel != null) { - try { + /** + * Close the channel for the file passed in. + *

+ * Thread safe. + */ + public static void closeChannelFor(File fileToUnlock) { + FileChannel fileChannel = channelCache.remove(fileToUnlock); + if (fileChannel != null) { + try { fileChannel.close(); } - catch (IOException e) { + catch (IOException e) { // ignore } - } - } + } + } - public static boolean isLocked(File file) { - return channelCache.containsKey(file); - } + public static boolean isLocked(File file) { + return channelCache.containsKey(file); + } }