-
-
Notifications
You must be signed in to change notification settings - Fork 1.5k
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
Add MemMapFileStream. Fixes in memFiles. #7944
Conversation
lib/pure/memfiles.nim
Outdated
result = flushViewOfFile(f.mem, f.size.int32) == 0 | ||
if not result: | ||
let lastErr = osLastError() | ||
if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode: |
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.
Why is this particular error ignored?
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.
The long remarks
RemarksFlushing a range of a mapped view initiates writing of dirty pages within that range to the disk. Dirty pages are those whose contents have changed since the file view was mapped. The FlushViewOfFile function does not flush the file metadata, and it does not wait to return until the changes are flushed from the underlying hardware disk cache and physically written to disk. To flush all the dirty pages plus the metadata for the file and ensure that they are physically written to disk, call FlushViewOfFile and then call the FlushFileBuffers function.
When flushing a memory-mapped file over a network, FlushViewOfFile guarantees that the data has been written from the local computer, but not that the data resides on the remote computer. The server can cache the data on the remote side. Therefore, FlushViewOfFile can return before the data has been physically written to disk.
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.
Oh!
I found this code: MappedByteBuffer.c#L66
retry = 0;
do {
result = FlushViewOfFile(a, (DWORD)len);
if ((result != 0) || (GetLastError() != ERROR_LOCK_VIOLATION))
break;
retry++;
} while (retry < 3);
``
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.
That code retries the flushing operation a maximum of 3 times. After 3 times, if the lock error occurs, it is still raised.
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.
I see.
Therefore I ignore ERROR_LOCK_VIOLATION
.
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.
Maybe add a similar code both for Windows and for Posix?
Please fix the build errors. It appears the code breaks when string tainting mode is turned on. |
Why bugs in memfiles didn't appear in the tests before? |
Ready for something. |
tests/stdlib/tmemmapstreams.nim
Outdated
import os, streams | ||
var | ||
mms: MemMapFileStream | ||
fn = "test.mmapstream" |
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.
Use a const
for fn
.
lib/pure/memfiles.nim
Outdated
result = flushViewOfFile(f.mem, 0) != 0 | ||
if not result: | ||
let lastErr = osLastError() | ||
if lastErr != ERROR_LOCK_VIOLATION.OSErrorCode: |
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.
This shouldn't be ignored. The C code this was based on tries a maximum of 3 times iff ERROR_LOCK_VIOLATION
is raised each time. The 4th time, ERROR_LOCK_VIOLATION
is raised.
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.
So I propose proc flush*(f: var MemFile, triesNumber: int = 3): bool
both for Windows and Posix.
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.
Why would that return bool
? Nim's stdlib uses exceptions.
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.
You can call flush
and if result == false then repeat the call. Can be useful, IMHO.
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.
You might as well catch the exception then. And why would I repeat the call myself when it didn't succeed after triesNumber
?
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.
I followed the logic proc open*(f: var File, ...): bool
.
So
- add
triesNumber
(ornumTries
)? - remove bool result?
- no raise an exception inside
flush
?
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.
- raise an exception for
MemMapFileStream.flush
?
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.
proc flush(f: var MemFile; attempts = 3) {.raises: [IOError].}
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.
I see inconsistency here: flush
for FileStream
don't raises exception.
flushFile
has WriteIOEffect
tag only.
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.
flushFile
is allowed to raise that and should be annotated too.
lib/pure/memfiles.nim
Outdated
@@ -245,6 +249,30 @@ proc open*(filename: string, mode: FileMode = fmRead, | |||
if close(result.handle) == 0: | |||
result.handle = -1 | |||
|
|||
proc flush*(f: var MemFile; attempts: int = 3) = |
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.
Why would flushing require multiple attempts?
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.
Ahh, the hidden reviews contain the answers. The original code also contains this really useful comment which should be copied into our code IMO.
lib/pure/streams.nim
Outdated
@@ -437,6 +438,71 @@ when not defined(js): | |||
else: | |||
raise newEIO("cannot open file") | |||
|
|||
type | |||
MemMapFileStream* = ref MemMapFileStreamObj ## a stream that encapsulates a `MemFile` |
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.
This should be in the memfiles
module.
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.
And StringStream in the strutils?
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.
No because string
is in system
.
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.
So, move to memfiles?
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.
Yeah, please.
lib/pure/memfiles.nim
Outdated
var res = false | ||
while retry <= attempts: | ||
res = flushViewOfFile(f.mem, 0) != 0 | ||
if res: |
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.
Also, you're retrying no matter what the error is, that's bad. Only retry on the ERROR_LOCK_VIOLATION
error!
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.
I don't see why replicating this to POSIX makes sense. In fact, I'm skeptical about whether this should be configurable via a param at all.
lib/pure/memfiles.nim
Outdated
## Flushes `f`'s buffer for the number of attempts equal to `attempts`. | ||
## If were errors an exception `OSError` will be raised. | ||
when defined(windows): | ||
var retry = 0 |
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.
Are for
loops outdated now?
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.
Oops, С-thinking. :)
All a bit complicated. :) |
You're still not checking the error code on Windows and are performing the retries on Linux. |
Yes, and now it's correct, IMO.
Why not? |
lib/pure/memfiles.nim
Outdated
when defined(windows): | ||
var res = false | ||
for i in 1..attempts: | ||
res = flushViewOfFile(f.mem, 0) != 0 |
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.
Again, what dom said, why not base the retry on the return value of flushViewOfFile
?
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.
Currently, an exception will be raised due to any OS error.
The developer has to decide what to do next.
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.
Ignoring some specific errors, it's a bad design.
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.
No, it's retrying only on specific errors, it's good design. Of course you should raise an exception for any other error.
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.
Well, then I need to investigate/experiments with flags and error codes of msync, too. :)
lib/pure/memfiles.nim
Outdated
@@ -245,6 +249,26 @@ proc open*(filename: string, mode: FileMode = fmRead, | |||
if close(result.handle) == 0: | |||
result.handle = -1 | |||
|
|||
proc flush*(f: var MemFile; attempts: uint = 3) = |
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.
Use Natural
instead of uint
here.
These retries are only for a specific Windows-only error. They do not apply to Linux. |
To be more precise,
|
Okay, so if it's appropriate to retry on that error code then do the retry, but don't retry on any error. |
Maybe to do a pause between attempts? |
No. |
Ready for merge, I hope. |
Good enough I suppose :) |
No description provided.