Move File and IO::FileDescriptor's platform-specific parts to Crystal::System#5333
Conversation
b0e0631 to
fd81b4d
Compare
|
In the second commit, the extracted platform-specific methods are prefixed I'd prefer |
|
I'll wait a few days to see if there is consensus on naming. Then I'll pick one and make the commit message and diff match up. If the only problem you could find is naming, I'm happy! |
| raise "Invalid access mode #{mode}" | ||
| end | ||
| else | ||
| raise "Invalid access mode #{mode}" |
There was a problem hiding this comment.
Doesn't support a mode like rb+. Also, #{mode.inspect} would make it clearer in the output what comes from the user and what's the error message itself
There was a problem hiding this comment.
This is directly copied from the old file.cr if it has issues, it's out of the scope of this PR.
src/crystal/system/unix/file.cr
Outdated
| path = "#{tmpdir}#{name}.XXXXXX#{extension}" | ||
|
|
||
| if extension | ||
| fd = LibC.mkstemps(path, extension.bytesize) |
There was a problem hiding this comment.
mkstemps(3) reads: Since it [the template] will be modified, template must not be a string constant, but should be declared as a character array. That's not obvious from the function itself (At least to a non-C user), there should be atleast a comment noting that the string will be modified (Which isn't "possible" from Crystal), or it could be copied into a temporary Bytes.
src/crystal/system/unix/file.cr
Outdated
|
|
||
| def stat(path) | ||
| if LibC.stat(path.check_no_null_byte, out stat) != 0 | ||
| raise Errno.new("Unable to get stat for '#{path}'") |
There was a problem hiding this comment.
path.inspect would give proper visual "escaping"
src/crystal/system/unix/file.cr
Outdated
|
|
||
| def real_path(path) | ||
| real_path_ptr = LibC.realpath(path, nil) | ||
| raise Errno.new("Error resolving real path of #{path}") unless real_path_ptr |
There was a problem hiding this comment.
Same path.inspect, this and following error messages omit the single-quote. (Consistency)
|
I think there needs to be a proper support for platform-specific functionality in the stdlib. IMHO, throwing stuff out like Truth is, programs tend to need platform-specific stuff that's not yet nicely wrapped. I doubt that you'll be able to offer everything that the control functions support in an agnostic manner. And many programs are fine if they don't work on some random platform, as long they work where they're intended to be used. Diminishing all API to the most common denominator will only hurt on the long run. Outside the scope of this PR, I'd actually like to see a documentation flag to note platform information right in the in-source docs. The docs generator could then mark such a section visually, or add a "Tag badge" to it. |
|
re: removing |
|
I appreciate these comments, but comments (in support of or against) about the approach of using a mixin module from |
There was a problem hiding this comment.
I'm not sure mixins are better than composition, but that should really help going forward, so I'm gonna say 👍 ! I guess we'll probably rework it later when there is windows support, but... later. Let's go forward —as long as the public API is good and stable.
I don't think we need the Mixin suffix when naming modules, I mean include Crystal::System::FileDescriptor is just fine, and the filenames don't have the _mixin suffix anyway (crystal/system/file_descriptor.cr).
About platform-specific methods, there are usages of the _impl suffix in different places in the stdlib. Maybe we could stick to that? My point is that "private impl" can englob the "target-specific" meaning.
src/file.cr
Outdated
| require "crystal/system/file" | ||
|
|
||
| class File < IO::FileDescriptor | ||
| private alias Sys = Crystal::System::File |
There was a problem hiding this comment.
I'd avoid the alias and use the long naming.
| # TODO: use fcntl/lockf instead of flock (which doesn't lock over NFS) | ||
| # TODO: always use non-blocking locks, yield fiber until resource becomes available | ||
|
|
||
| def flock_shared(blocking = true) |
There was a problem hiding this comment.
Would it be opportune to break the file lock API now? The current flock naming and API is tied to the underlying flock POSIX implementation, which is bad. What about abstracting it now, since this patch is meant to support windows, eventually.
enum Lock
Shared
Exclusive
end
def lock(operation : Lock, blocking = false, &block)
end
def lock(operation : Lock, blocking = false)
end
def unlock
endThere was a problem hiding this comment.
I'd tentatively say that's easily done in a latter PR - and so it should be.
There was a problem hiding this comment.
So be it. Let's stick to extracting posix-specific calls in this PR.
There was a problem hiding this comment.
I'd recommend making an issue to track that though, we seem to forget a lot of these refactors once the PR gets merged.
|
@ysbaddaden if you look at |
|
Hum, I prefer to use Fairly minor, but it looks better to my eyes. |
|
@ysbaddaden sounds good to me. I'll change it. |
|
I'm still undecided about naming. I think a |
|
To me it boils down to "implementation detail", but that's pedantic nitpicking, no need to spend hours on such details. |
fd81b4d to
70bb1e0
Compare
|
Updated this PR. Changed the naming to |
|
Also, if this gets merged, please make sure to rebase, not just squash. I think it'll make for a better commit history. |
| private def unbuffered_rewind | ||
| seek(0, IO::Seek::Set) | ||
| self | ||
| self.pos = 0 |
There was a problem hiding this comment.
pos=(value) calls seek(value) and Seek::Set is default. So seek(0) would be the most direct call here.
There was a problem hiding this comment.
No reason for the change, it's trivial to see it's the same but I can revert it if you want.
| end | ||
| end | ||
|
|
||
| private def system_pos |
There was a problem hiding this comment.
I'd prefer system_tell over system_pos.
I wish we'd drop IO#pos and IO#pos= and kept IO#seek and IO#tell instead (less duplication, more explicit naming).
There was a problem hiding this comment.
We just need 1 call: seek which always returns the current position. I suggested that IO implement pos pos= and tell in the base class to @asterite and simply provide a raising IO#tell by default.
For now, this is how IO works. It's not great, but it is how it is until I refactor.
There was a problem hiding this comment.
Personally i'd rather keep pos, pos= and seek. tell is obscure and weird naming to me.
There was a problem hiding this comment.
Well, it's "seek and tell". They're well known associated terms, whatever the language. E.g. C, Haskell, Python, Ruby or Rust, all have both.
I don't like the pos naming, it's an abbreviation and inexplicit (inherited from Ruby). At least position would be a more explicit name but I still prefer tell (broadly used). Using a setter (pos=) to perform an action, that is move the cursor in a file, sounds awful to me, in addition to be an exact duplicate for seek, too.
There was a problem hiding this comment.
I have to say "seek" is not an unknown term to me, although "tell" is. I agree with you on pos vs position, but not that setters shouldn't have performance side-effects.
There was a problem hiding this comment.
As far as I can tell, the terms tell and seek come from working with files. IO is a general class, and to me it doesm't seem logical to transfer this meaning from files to any other stream. But I may be mistaken.
Another thought: pos and pos= is a concept easy to understand. One reads the current stream position, the other sets it (position and position= are a bit longer but also fine). With seek and tell it is more complicated, if your not familiar with these terms. You have to remember two names instead of one and can't simply asseses their meaning.
There was a problem hiding this comment.
I'm also for dropping #seek/#tell and using #position/#position= (Not #pos). The latter is really clear in intention. Also, it lets you seek around in a file using normal code:
my_file.position += 5 # This is how you'd write it anywhere else too! Fantastic!
my_file.seek(my_file.tell + 5) # Compared to the one above: Meh.
my_file.seek(5, File::SEEK_CUR) # I can never remember which SEEK_* I need - this sucks.There was a problem hiding this comment.
I think seek should stay, io.position = io.size - 5 involves 2 syscalls whereas io.seek(-5, IO::Seek::End) requires only one. Similar for io.position += 5. That being said, I think that using io.position is a much nicer interface which is more than adequate for most people. Perhaps some caching for size and position would mean that we can obviate seek entirely.
Regardless of that, we should make a new issue containing all the IO refactor suggestions from this thread. Unless anyone else volunteers to make that issue now, i'll get it done once this PR is merged.
| r | ||
| end | ||
|
|
||
| def fcntl(cmd, arg = 0) |
There was a problem hiding this comment.
IO#fcntl method is supposed to be public, but it won't be documented?
There was a problem hiding this comment.
It wasn't documented before, I can add documentation if you want.
There was a problem hiding this comment.
No, I meant: will the #fcntl method still appear in the documentation?
There was a problem hiding this comment.
Hmm, not sure. Let me check.
There was a problem hiding this comment.
They actually break the doc generator by saying they're included from the module, but the module is :nodoc: so that the links don't work.
There was a problem hiding this comment.
I'll go and do this method the same as the rest - with the system_ prefix and {% ifdef %} the method out.
There was a problem hiding this comment.
Yes, I've noticed this, too. Inclusion and extension of modules that are not documented should probably be hidden in the docs generator.
There was a problem hiding this comment.
I pushed a fixup commit which resolves this. If this is OK i'll rebase to allow this to be merged.
|
There have been loads of great ideas about refactoring If people feel that some of that work would be better in this PR however, i'm happy to include it. |
|
Plus, a larger scope pf the PR means I have the chance to perform these same refactors on |
|
Ping? I think I've solved all the reviews, and all this should need is a tiny review of the fixup commit from @ysbaddaden or a review from another core team member. I think this PR is fine to land in 0.24.1. |
In order to add windows support to Crystal, IO::FileDescriptor cannot depend on posix APIs. Here, we refactor out the platform-specific parts of IO::FileDescriptor into a mixin module in Crystal::System. Some platform-specific methods are refactored out of methods which contain both platform-specific and cross-platform behaviour. These methods are prefixed with `system_` and are private.
Making Tempfile inherit from File allows the instance to be passed to methods which expect File, and makes Tempfile much more powerful. This requires adding a new private constructor to File which sets @path and calls super.
Similar to previous patch: Separate platform-specific parts of IO::FileDescriptor.
ccdd6b7 to
586846d
Compare
|
Build failure is known related to travis's new build images: travis-ci/travis-ci#8891. |
Hopefully the third time lucky we'll manage to merge an IO portability refactor.
I've copied the commit messages out below so people actually read them. I also suggest you review each commit in order, instead of reviewing the diff as a whole. it makes the entire thing a lot more readable and understandable.
Make IO::FileDescriptor fcntl private
fcntl() is an extremely low-level platform-specific detail which should only be used internally to IO::FileDescriptor. In this commit, it is removed from the public API and made private.
The only usage of this API was added only recently, in Crystal::Main. It is replaced with a method which calls LibC.fcntl directly, since it is too early in init to create a new IO::FileDescriptor instance directly and ask it if it's blocking.
Separate platform-specific parts of IO::FileDescriptor
In order to add windows support to Crystal, IO::FileDescriptor cannot depend on posix APIs. Here, we refactor out the platform-specific parts of IO::FileDescriptor into a mixin module in Crystal::System. Some platform-specific methods are refactored out of methods which contain both platform-specific and cross-platform behaviour. These methods are prefixed with
system_and are private.Make Tempfile inherit File
Making Tempfile inherit from File allows the instance to be passed to methods which expect File, and makes Tempfile much more powerful. This requires adding a new private constructor to File which sets @path and calls super.
Separate platform-specific parts of File
Similar to previous patch: Separate platform-specific parts of IO::FileDescriptor.