-
Notifications
You must be signed in to change notification settings - Fork 207
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Filestore thread safety fixes #295
Changes from all commits
a5ecb8a
28f8e12
d7ff9fe
bb44396
7e17994
cc5a7cf
0672281
761cb3d
a1f574f
1dab5ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,8 +9,15 @@ | |
import java.io.Writer; | ||
import java.util.ArrayList; | ||
import java.util.Arrays; | ||
import java.util.Collection; | ||
import java.util.Collections; | ||
import java.util.Comparator; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.concurrent.ConcurrentLinkedQueue; | ||
import java.util.concurrent.ConcurrentSkipListSet; | ||
import java.util.concurrent.locks.Lock; | ||
import java.util.concurrent.locks.ReentrantLock; | ||
|
||
abstract class FileStore<T extends JsonStream.Streamable> { | ||
|
||
|
@@ -21,6 +28,9 @@ abstract class FileStore<T extends JsonStream.Streamable> { | |
private final int maxStoreCount; | ||
private final Comparator<File> comparator; | ||
|
||
final Lock lock = new ReentrantLock(); | ||
final Collection<File> queuedFiles = new ConcurrentSkipListSet<>(); | ||
|
||
FileStore(@NonNull Configuration config, @NonNull Context appContext, String folder, | ||
int maxStoreCount, Comparator<File> comparator) { | ||
this.config = config; | ||
|
@@ -54,20 +64,28 @@ String write(@NonNull T streamable) { | |
File exceptionDir = new File(storeDirectory); | ||
if (exceptionDir.isDirectory()) { | ||
File[] files = exceptionDir.listFiles(); | ||
|
||
if (files != null && files.length >= maxStoreCount) { | ||
// Sort files then delete the first one (oldest timestamp) | ||
Arrays.sort(files, comparator); | ||
Logger.warn(String.format("Discarding oldest error as stored " | ||
+ "error limit reached (%s)", files[0].getPath())); | ||
if (!files[0].delete()) { | ||
files[0].deleteOnExit(); | ||
|
||
for (int k = 0; k < files.length && files.length >= maxStoreCount; k++) { | ||
File oldestFile = files[k]; | ||
|
||
if (!queuedFiles.contains(oldestFile)) { | ||
Logger.warn(String.format("Discarding oldest error as stored " | ||
+ "error limit reached (%s)", oldestFile.getPath())); | ||
deleteStoredFiles(Collections.singleton(oldestFile)); | ||
} | ||
} | ||
} | ||
} | ||
|
||
String filename = getFilename(streamable); | ||
|
||
Writer out = null; | ||
lock.lock(); | ||
|
||
try { | ||
out = new FileWriter(filename); | ||
|
||
|
@@ -82,26 +100,67 @@ String write(@NonNull T streamable) { | |
filename), exception); | ||
} finally { | ||
IOUtils.closeQuietly(out); | ||
lock.unlock(); | ||
} | ||
return null; | ||
} | ||
|
||
@NonNull abstract String getFilename(T streamable); | ||
@NonNull | ||
abstract String getFilename(T streamable); | ||
|
||
List<File> findStoredFiles() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think this can be called multiple times in the current design, which might cause bad things to happen. Potentially this should only return files that are not already in the queued files set? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good point. I've updated this method so that it only returns stored files which aren't in the |
||
List<File> files = new ArrayList<>(); | ||
lock.lock(); | ||
try { | ||
List<File> files = new ArrayList<>(); | ||
|
||
if (storeDirectory != null) { | ||
File dir = new File(storeDirectory); | ||
if (storeDirectory != null) { | ||
File dir = new File(storeDirectory); | ||
|
||
if (dir.exists() && dir.isDirectory()) { | ||
File[] values = dir.listFiles(); | ||
if (dir.exists() && dir.isDirectory()) { | ||
File[] values = dir.listFiles(); | ||
|
||
if (values != null) { | ||
files.addAll(Arrays.asList(values)); | ||
if (values != null) { | ||
for (File value : values) { | ||
if (value.isFile() && !queuedFiles.contains(value)) { | ||
files.add(value); | ||
} | ||
} | ||
} | ||
} | ||
} | ||
queuedFiles.addAll(files); | ||
return files; | ||
} finally { | ||
lock.unlock(); | ||
} | ||
return files; | ||
} | ||
|
||
void cancelQueuedFiles(Collection<File> files) { | ||
lock.lock(); | ||
try { | ||
if (files != null) { | ||
queuedFiles.removeAll(files); | ||
} | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
void deleteStoredFiles(Collection<File> storedFiles) { | ||
lock.lock(); | ||
try { | ||
if (storedFiles != null) { | ||
queuedFiles.removeAll(storedFiles); | ||
|
||
for (File storedFile : storedFiles) { | ||
if (!storedFile.delete()) { | ||
storedFile.deleteOnExit(); | ||
} | ||
} | ||
} | ||
} finally { | ||
lock.unlock(); | ||
} | ||
} | ||
|
||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a chance this can fail if IOUtils throws?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NVM, I just looked up what
IOUtils.closeQuietly
does