[racket-dev] Safe errno access across platforms.

From: Nathan (nejucomo at gmail.com)
Date: Wed Dec 5 15:46:19 EST 2012

On Tue, Dec 4, 2012 at 3:56 PM, Matthew Flatt <mflatt at cs.utah.edu> wrote:
> I'm not sure...
>
> Providing error-code information is an appealing idea. Instead of
> `exn:fail:errno', we'd probably add a `prop:exn:error-code' property
> that is implemented by subtypes of `exn:fail:filesystem', etc. Also,
> the value of the property would need to provide both a code and a
> "kind" for the code, such as 'posix for a code from from `errno' or
> 'windows for a code from GetLastError(). The way that error codes are
> currently included in the text of error messages could be leveraged to
> get them into an exception structure without too many changes.
>
> I'm not sure about `errno->symbol' and how to make it both portable and
> general, though. I can imagine an `errno->symbol' that works with the
> most common and standard error results, but I think there must be some
> platform variation, to say nothing of Windows error code versus Posix
> `errno' values. Does some other high-level language deal with error
> codes in a good way that we could imitate?

This is a good point.  I was implicitly following python here, which,
to its credit, exposes details from the platform fairly directly, and
which, to its detriment, sometimes lacks a clear "platform
independent" abstraction.

>
>
> Then again, I worry that dealing with error codes in a general way is a
> lot of work for a feature that probably won't find much use. That is, I
> actually disagree with your claim that "it is often desirable to
> distinguish error causes with as fine a resolution as the platform
> provides"; it seems fairly rare to me --- aside from including
> information in error messages, which Racket already does.

Hm, I'm not sure what you mean by "a general way".

Exposing the underlying operating system data to discriminate errors
should just be about some factor times N work, where N is the number
of platforms racket supports, right?

OTOH, creating rich cross-platform exception hierarchies *would* be a
lot of work.

>
> We have mostly dealt with interesting error cases by making new
> exception subtypes, such as `exn:fail:filesystem:exists'. It might be
> that `exn:fail:filesystem:not-exists' is useful to add. If that's the
> only immediate need, though, maybe we should just add that for now.
>

Ok, so creating a rich cross-platform exception hierarchy *does* sound
like a lot of work, and I agree that it's best to only enrich the
hierarchy when there's a pressing need.

However, without exposing the underlying details to application
programmers, *some* applications are left with hacks like parsing
strerror output.  I still advocate for exposing the platform specific
error codes making it clear that those details are not cross-platform
and leaving it up to the application developer.

Maybe I'm biased from my experience, but I've often worked on or
maintained python applications where a given application will
initially be deployed on a single platform, so general abstractions
are not strictly necessary.

Here's my understanding of the trade-offs, where I anticipate the
"application effort" which is the effort for *new* applications which
need some distinction between errors, versus "backwards compatibility"
which is the effort required by pre-existing applications which do not
care about these distinctions:


Approach 1: Do nothing.

Racket effort: 0

Application effort: Moderate / Messy - parse exception messages
strings, accounting for locale and platform issues for the
application's target audience. (messy, laborious)

Backwards Compatibility: perfect - No work necessary for other applications.


Approach 2: Expose underlying error values directly and unambiguously
without any abstraction.

Racket effort: small (? I'm guessing here)

Application effort: Moderate / Slightly cleaner - Create conditional
error handlers depending on the platform and specific error cases
needed by the application.

Backwards Compatibility: I'm not sure, but foresee 3 possibilities:
  - Perfect compatibility; The error values are retrievable in a
manner that doesn't affect any structures or pre-existing apis.  (For
example a get-current-errno function.)

  - Case-specific incompatibility; Some structures have new fields,
only code that matches/manipulates those structures need to be
changed.


Approach 3: Enrich the exception hierarchy in a cross-platform, but
more specific way

Racket effort: large

Application effort: Small / Clean - Applications have the perfect
distinction in the error hierarchy for their use case and it's mapped
correctly to the target platforms

Backwards Compatibility: Two options:

 - Fair: The existing hierarchy is not modified, only new branches are added.

 - Poor: The existing hierarchy is modified; many existing
applications must be altered.


Does this seem accurate?

I favor approach 2 because it's minimal work for racket, and could be
done in a manner that's perfectly or nearly backwards compatible.  It
exposes non-portable values with some api, so the documentation should
just clearly indicate this.  This gives applications which really need
some distinction a clean starting point, though they have to manage
the platform issues.

Actually, I haven't yet learned at all about the ffi, so it could be
there's an even less disruptive option:  Make ffi modules for various
platforms that extract error information, to be used inside exception
handlers.  This would require no modifications to racket, but would
give some specialized applications the tool they need.


>
> At Mon, 3 Dec 2012 14:37:36 -0800, Nathan wrote:
>> Hello,
>>
>> I'm new to racket, and only mildly familiar with scheme, so I
>> apologize if I'm missing important details.
>>
>> Whenever an error occurs related to a C interface which uses "errno"
>> to signal the kind of error, I propose the associated racket exception
>> should capture this errno value separately from the error message.  I
>> propose exposing the literal value and also a cross-platform way to
>> determine the symbolic name for that value on the current platform.
>>
>> The reason is that it is often desirable to distinguish error causes
>> with as fine a resolution as the platform provides.  The specific
>> example that lead me to this issue is that I want to read a file, but,
>> in the case that the file is not present, return a default value.  In
>> any other case (such as incorrect permissions, or should the path
>> refer to a directory instead of a file), I want the error to
>> propagate.  A work-around for my particular case is to check for the
>> file's existence before opening it for reading, but notice that this
>> introduces one more race-condition, so I prefer my original strategy.
>> This is just a single case, and I expect in general it will be useful
>> or necessary to distinguish errno values.
>>
>> I chatted with dyoo on the freenode irc channel and he suggested I
>> send mail to this list, because this would be a backwards incompatible
>> change.
>>
>> I'm not too familiar with racket, so there may be much better API
>> designs, but my first idea was to add a new base exception struct
>> along the lines of (struct exn:fail:errno exn:fail (errno)) and some
>> separate module for translation with errno->symbol and symbol->errno.
>> Maybe it's nicer to just include the symbol directly in the exception
>> structure.
>>
>> There are a few implementation details I'd caution about:
>>
>> In general, "errno" may be a macro, so a simple ffi wrapper may not be
>> cross-platform (according to the manpage).  The value of errno is in
>> thread-local storage, and I have no idea how that relates to racket
>> thread safety.
>>
>> Including *only* the symbolic name of an errno would be a mistake,
>> IMO, because I'm an anti-fan of abstractions hiding the truth.  OTOH,
>> I'm also a fan of abstractions and interfaces, so excluding the
>> symbolic name is essential for writing clean cross-platform code.
>>
>>
>> Regards,
>> Nathan Wilcox
>>
>> ps: So far I'm really enjoying racket!  The documentation is
>> excellent; there's a debian package I can simply apt-get; and I've
>> been playing with the custom language design features which are much
>> nicer than I realized for scheme-likes.  I had always thought the
>> status quo was splicing with quasiquote so the syntax features are a
>> wonderful surprise.  Also, I'm very interested to explore the
>> sandboxing features, but I haven't yet learned them in detail.  I'm
>> quite pleased they include time and memory constraints.
>> _________________________
>>   Racket Developers list:
>>   http://lists.racket-lang.org/dev


Regards,
Nathan Wilcox

Posted on the dev mailing list.