[sane-devel] SANE V2

Matto Marjanovic maddog@mir.com
Tue, 10 Dec 2002 13:03:00 -0500


Gooooood morning,

 >If I understand correctly what happens in the case of auth_callback,
 >the remote sane_func isn't finished yet, the callback is still running.
 >
 >>   o Network backend calls the client frontend's callback with string,
 >>      then finally returns from "sane_func()" request.
 >
 >First the net backend calls the RPC for sane_func again, saned returns
 >from the clalbach, the remote frontend returns from sane_func() and
 >give the result back all the way to the local frontend.

Yeah, this/that occurred to me later last night.  
  o When the device backend invokes the callback, saned can return an early
     response to the client backend "I'm not done with the last operation,
     but please invoke a callback for me."
  o The client net backend invokes the real callback, and then sends the
     return status to saned in another RPC call "Here is the result of the
     callback; now finish what you were doing.".
  o Saned returns the result to the device backend, and everything keeps
     on going as before (i.e. eventually the backend returns from whatever
     sane_func(), and then saned sends a closing response to the client).

Is this how it is implemented?  (I took a quick look at the net backend,
 and discovered that a quick look is not enough.)  Neither the current SANE
 standard nor the v2 draft say anything about callbacks.

The big question I have is:  How is an asynchronous sane_cancel() invoked???


 [getting back to verbose errors...]
 ...
 >As far as I understood the comment about thread-safety (from David
 >Mosberger-Tang) is about interface definition, not implementation.
 ...
 >So I guess the following won't hurt:
 >
 >void
 >sane_verbose_error(Sane_Handle h, SANE_String * verbose_message,
 >                   SANE_Int lenght)
 >
 >The the frontend can decide what to do with the string, e.g. print it
 >later.

I don't see how it helps, and I think it does hurt:

a) This doesn't have any impact on thread safety:  this function still cannot
    be called while another sane_func(h, ...) is executing --- that other
    function might be in the middle of rewriting the backend's "verbose
    message buffer".
   Furthermore, without the requirement that this function is called
    synchronously, it becomes ambiguous which error it refers to.

b) Now the frontend has to preallocate a buffer to receive the message,
    which will probably only be read once.  How big should that buffer be?
    How will the frontend know if the message has been truncated because
    its buffer is too small?

In the original version, if the frontend wants to keep the string around
 after the next sane_func(h, ...), it can just strdup() it.


Hmm... you know what, we've overlooked something.
What about a verbose error message for those functions which don't require
 a device handle?
(back to this in a moment)

 ...
 >As far as I understood the comment about thread-safety (from David
 >Mosberger-Tang) is about interface definition, not implementation.
 >
 >So even if the current implementation doesn't allow simuktaneous
 >operation of SANE function, a later one may do. And we are in trouble
 >then. All the other function definitions seem to be thread-safe.

Ok, this is worthy of some discussion.

I don't see any current guarantees of thread-safety anywhere in SANE.
 On the day that we amend the standard to say "these functions may be
 called simultaneously", we will all be screwed anyway, because none of
 the backends will have been written with thread-safety in mind.

We need to explicitly define which operations can be called at which times.

Here is some brainstorming:

  First, let's consider the device-dependent functions:
   
       SANE_Status sane_control_option(H, )
       SANE_Status sane_get_parameters(H, )
       SANE_Status sane_start(H, )
       SANE_Status sane_read(H, )

       void sane_cancel(H, )
       SANE_Option_Descriptor * sane_get_option_descriptor(H, )

       void sane_verbose_error(H, )
       void sane_close(H)

     These all act on a particular device handle, H.  It should be
      perfectly safe to call these functions simultaneously and reentrantly
      for different handles, but not necessarily for the same handle.
   
     The first four are all synchronous operations --- it doesn't make
      any sense to call any of them simultaneously, for a given handle H.
      (E.g. You can't get the scan parameters while you are in the
      middle of setting an option, and vice-versa.)

     sane_cancel() is signal-safe, and might as well be thread-safe, too.
      One should be allowed to call sane_cancel() at any time.

     sane_get_option_descriptor() returns a pointer to a descriptor, and
      that pointer is guaranteed to be valid as long as the device is
      open.  So, technically, this function could be thread-safe and
      signal-safe, too, since it is just returning pointers which are 
      presumably allocated once during sane_open().
     However, the values of those option_descriptors --- the stuff that
      is *pointed to* --- is not guaranteed to be valid for the duration
      of a call to sane_control_option().  Setting an option may change
      the ranges/etc of other options.
     So, the option_descriptors themselves must be accessed synchronously,
      more or less, for a given handle H.

     sane_verbose_error(), as we've discussed, reflects the return status
      of the last synchronous operation, hence it must also be called
      synchronously (for a given handle H).

     sane_close() invalidates the device, so it also must be called
      synchronously.  It is probably be a good idea to declare that it
      is illegal to call sane_cancel(H, ) during a call to sane_close(H, ).


  Ok, now the device-independent functions:

       SANE_Status sane_init()
       void sane_exit()

       SANE_String_Const sane_strstatus()

       SANE_Status sane_get_devices()
       SANE_Status sane_open()

     sane_init() and sane_exit() are definitely synchronous -- one has to be
      called before anything else, and one has to be called after everything
      else.

     sane_strstatus() should be completely thread and signal safe.  It is
      not even a backend function, technically, and just returns a string
      constant.

     sane_get_devices() appears to be synchronous, given this language in
      its description (4.3.3):
          "The returned list is guaranteed to remain unchanged and
           valid until (a) another call to this function or (b) a call
           to sane_exit() is performed."
      Thus, a second thread calling this function could silently invalidate
      the list being accessed by the first thread.

     sane_open() looks like it *could* be made re-entrant; there doesn't
      seem to be anything about the code-flow the prevents it.  But this
      may require some mutex mechanism in the backend.
     However, sane_open() should be declared thread-safe with respect
      to the device-dependent functions above.

     I don't see why sane_open() would ever need to be called more than
      once simultaneously.  But I can imagine multi-threading being used
      (in a network daemon, for example) such that there was one thread
      assigned to each open device, and a master thread in charge of
      handling new device requests.  The master thread would be the only
      thread that ever needed to call sane_open(), once at a time.


  Hmmm... so, back to sane_verbose_error(H, ):

     sane_init(), sane_open(), and sane_get_devices() all return a
      SANE_Status, and for the first two, at least, one might want to see
      a more verbose message/explanation from the backend.

     If these functions are all required to be called synchronously, then
      we could simply say that

                 sane_verbose_error(NULL, ...)

      returns the "most recent global verbose message".

That's what I got so far.  Ideas?


-matt "get it in writing" m.