|
Quick Lists
|
|
Bug ID:
|
6631352
|
|
Votes
|
0
|
|
Synopsis
|
File{OutputStream,Writer} should implement atomic append mode using FILE_APPEND_DATA (win)
|
|
Category
|
java:classes_io
|
|
Reported Against
|
|
|
Release Fixed
|
7(b25)
|
|
State
|
10-Fix Delivered,
bug
|
|
Priority:
|
3-Medium
|
|
Related Bugs
|
4960438
,
6633375
|
|
Submit Date
|
17-NOV-2007
|
|
Description
|
It is possible to open a FileOutputStream (and FileWriter) in append mode.
This is implemented on Windows by opening the underlying file in the ordinary way,
and seeking to the end of the file before writing to the file.
Presumably this was done because it was not obvious how to open a
Windows file HANDLE in append mode.
This technique has two deficits:
It is not atomic. If multiple threads append to a file,
one of the appends may get lost.
If the underlying FileDescriptor or HANDLE is extracted from the FileOutputStream,
it is open in ordinary write mode, so does not inherit "append mode" semantics.
A "natively" append-mode HANDLE appears to be necessary to implement
append mode redirection, as needed by
4960438: (process) Need IO redirection API for subprocesses
Posted Date : 2007-11-17 17:57:29.0
|
|
Work Around
|
N/A
|
|
Evaluation
|
Indeed.
If we look at the CreateFile API documentation
http://msdn2.microsoft.com/en-us/library/aa363858.aspx
------------
You can get atomic append by opening a file with FILE_APPEND_DATA access and
_without_ FILE_WRITE_DATA access.
If you do this then all writes will ignore the current file pointer and
be done at the end-of file.
The append behavior is properly synchronized between multiple writes
(with or without multiple handles),
where the typical way I've seen this implemented (by seeking to EOF and then writing)
has a race condition if multiple threads / processes are appending to the same file.
------------
@@ -190,9 +190,16 @@
jlong
winFileHandleOpen(JNIEnv *env, jstring path, int flags)
{
+ /* To implement O_APPEND, we use the strategy from
+ http://msdn2.microsoft.com/en-us/library/aa363858.aspx
+ "You can get atomic append by opening a file with
+ FILE_APPEND_DATA access and _without_ FILE_WRITE_DATA access.
+ If you do this then all writes will ignore the current file
+ pointer and be done at the end-of file." */
const DWORD access =
- (flags & O_RDWR) ? (GENERIC_WRITE | GENERIC_READ) :
+ (flags & O_APPEND) ? FILE_APPEND_DATA :
(flags & O_WRONLY) ? GENERIC_WRITE :
+ (flags & O_RDWR) ? (GENERIC_READ | GENERIC_WRITE) :
GENERIC_READ;
const DWORD sharing =
FILE_SHARE_READ | FILE_SHARE_WRITE;
Posted Date : 2007-11-17 17:57:29.0
Here's a test case:
import java.io.File;
import java.io.FileOutputStream;
import java.util.concurrent.Executors;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
public class AtomicAppend {
// Before the fix for
// 6631352: Implement atomic append mode using FILE_APPEND_DATA (win)
// this would fail intermittently on windows
void test(String[] args) throws Throwable {
final int nThreads = 10;
final int writes = 1000;
final File file = new File("foo");
file.delete();
try {
final ExecutorService es = Executors.newFixedThreadPool(nThreads);
for (int i = 0; i < nThreads; i++)
es.execute(new Runnable() { public void run() {
try {
FileOutputStream s = new FileOutputStream(file, true);
for (int j = 0; j < 1000; j++) {
s.write((int) 'x');
s.flush();
}
s.close();
} catch (Throwable t) { unexpected(t); }}});
es.shutdown();
es.awaitTermination(10L, TimeUnit.MINUTES);
equal(file.length(), (long) (nThreads * writes));
} finally {
file.delete();
}
}
//--------------------- Infrastructure ---------------------------
volatile int passed = 0, failed = 0;
void pass() {passed++;}
void fail() {failed++; Thread.dumpStack();}
void fail(String msg) {System.err.println(msg); fail();}
void unexpected(Throwable t) {failed++; t.printStackTrace();}
void check(boolean cond) {if (cond) pass(); else fail();}
void equal(Object x, Object y) {
if (x == null ? y == null : x.equals(y)) pass();
else fail(x + " not equal to " + y);}
public static void main(String[] args) throws Throwable {
new AtomicAppend().instanceMain(args);}
void instanceMain(String[] args) throws Throwable {
try {test(args);} catch (Throwable t) {unexpected(t);}
System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed);
if (failed > 0) throw new AssertionError("Some tests failed");}
}
Posted Date : 2007-11-17 21:06:20.0
It is counterintuitive that the use of security permission flags like FILE_APPEND_DATA
should affect the runtime behavior of a file handle, but the win32 API
is not the best example of elegant software design.
Posted Date : 2007-11-17 21:25:45.0
|
|
Comments
|
PLEASE NOTE: JDK6 is formerly known as Project Mustang
|
|
|
 |