delegate_writer.anubis 7.26 KB
 Holder       : Matthieu Herrmann
 Author       : The Saunders Team
 Creation     : 2014/05/12
 Last update  : 2014/05/26 11:18 by matthieu@embryo (inserted by vim).
 =======================================================================================================================

  Overview:
    When performing multiple change on a buffer, we may want to limit the amount of write, e.g. when writing on disk.
  This library allows to do so, starting a counter when the first "save" command is issued, actyally writing the data
  at the end of the allocated time. Meanwhile, the data to be written can be altered.

  Note: This library only works with filename.

    You need an handler for your "Asynchronous Data", i.e. an opaque type making the bridge between your data and
  the file to write.
public type AData($D):...

    You can get a type with this function:
  * prefx is a prefix prepended to the filename. Use "" if you do not need one.
  * time_wait is the delay before writing the first save when the state is synchronised.
  * time_poll is the polling time while waiting for the actual writing to end (when the data are really begin written).
public define AData($D) make_AData(
    String                      prefx,          // A prefix
    String                      filename,       // Your filename
    ($D, String) -> SaveResult  writing_fun,    // Your writing function: the filename is passed as a parameter.
    Int                         time_wait,      // synchronisation time, in ms
    Word32                      time_poll       // Polling time, in ms
  ).

    Same as above with empty prefix, 10 seconds for time_wait an 50ms for time_poll
public define AData($D) make_AData(
    String                      filename,       // Your filename
    ($D, String) -> SaveResult  writing_fun     // Your writing function: the filename is passed as a parameter.
  ).

    Write the data on the handler.
  Note: the data must be "complete", i.e. when two "save" are called, only the data from the last one will
        actually be written !
public define One save($D d, AData($D) ad).

    You can set a prefix. Be sure to flush your data before using this function !
public define One set_prefix(String prfx, AData($D) ad).

    Force writing (do not wait, stop waiting).
public define One flush(AData($D) ad).

  ======================================================================================================================
  = Private part =======================================================================================================
  ======================================================================================================================

read tools/basis.anubis // Needed for "sleep"

  *** [1] Type definitions
      ==================================================================================================================

    This type allows to control the writing of the data:
type SynchroState($D):
  synchronised,             // Data are written
  unsynchronised($D data),  // Data are waiting for a write
  synchronising($D data).   // Data are being written on the disk

    The "Asynchronous Data" handler type
public type AData($D):
  adata(
    Var(String)                 prex,         // Prefix for your file
    String                      filename,     // The filename
    ($D, String) -> SaveResult  writing_fun,  // The writing function
    Var(SynchroState($D))       state_v,      // The synchronisation state, holding the data.
    Int                         time_wait,    // Waiting time before write
    Word32                      time_poll     // Polling time while writing
  ).

  *** [2] Internal tools/functions
      ==================================================================================================================

define One wait_for_end_of_synchronisation(AData($D) ad) =
  if ad is adata(_, _, _, state_v, _, tp) then
  checking every tp millisecond,
  wait for (if *state_v is synchronising(_) then false else true)
  then unique.

    "try_save" use "protect" and hence can not be recursive (auto-deadlock). It returns "false" on error.
define Bool try_save(AData($D) ad) =
  if ad is adata(prefx, filename, wf, state_v, tw, _) then
  sleep(tw);
  protect(
    if *state_v is
    {
      synchronised then should_not_happen(true)

      unsynchronised(data) then
        with path = if *prefx = "" then filename else *prefx + "/" + filename,
        state_v <- synchronising(data);
        if wf(data, path) is
          {
            cannot_open_file then
              state_v <- unsynchronised(data);
              println("Error: can not open file: " + filename + ". Trying again in "+tw+"ms.");
              false

            write_error then
              state_v <- unsynchronised(data);
              println("Error: can not write file: " + filename + ". Trying again in "+tw+"ms.");
              false

            ok then state_v <- synchronised; true
          }

      synchronising(_) then true
    }
  ).

    "delayed_save" uses "try_save" to perform the saving. If "false" is returned, it call itsel again (and again).
  The waiting is done in "try_save".
define One delayed_save(AData($D) ad) =
  if try_save(ad)
  then unique             // delegated machine will stop here
  else delayed_save(ad).  // Try again on error

  *** [3] Interface implementation
      ==================================================================================================================

public define AData($D) make_AData(
    String                      prefx,
    String                      filename,
    ($D, String) -> SaveResult  writing_fun,
    Int                         time_wait,
    Word32                      time_poll
  ) = adata(var(prefx), filename, writing_fun, var(synchronised), time_wait, time_poll).

public define AData($D) make_AData(
    String                      filename,
    ($D, String) -> SaveResult  writing_fun
  ) = make_AData("", filename, writing_fun, 10000, 50).

public define One save($D d, AData($D) ad) =
  if ad is adata(_, _, _, state_v, _, tp) then
  if * state_v is
  {
    synchronised then protect(
      state_v <- unsynchronised(d);
      delegate delayed_save(ad),
      unique
    )

    // Update the data to be written
    unsynchronised(_) then protect state_v <- unsynchronised(d)

    // Wait for end of current sync, relaunch save.
    synchronising(_)  then wait_for_end_of_synchronisation(ad); save(d, ad)
  }
  .

public define One flush(AData($D) ad) =
  if ad is adata(prefx, filename, wf, state_v, _, _) then
  if * state_v is
  {
    synchronised then unique

    // Force the writing
    unsynchronised(data) then protect(
      with path = if *prefx = "" then filename else *prefx + "/" + filename,
      state_v <- synchronising(data);
      if wf(data, path) is
      {
        cannot_open_file then
          state_v <- unsynchronised(data);
          println("Error: can not open file: " + filename + ".")

        write_error then
          state_v <- unsynchronised(data);
          println("Error: can not write file: " + filename + ".")

        ok then state_v <- synchronised
      }
    )

    // Wait for end of current sync
    synchronising(_)  then wait_for_end_of_synchronisation(ad)
  }
  .

public define One set_prefix(String prfx, AData($D) ad) = if ad is adata(prefx_v,_,_,_,_,_) then prefx_v <- prfx.