Proposed Asynchronous Control Flow Operators

From Erights

Revision as of 18:54, 29 November 2007 by Const (Talk)
(diff) ←Older revision | Current revision (diff) | Newer revision→ (diff)
Jump to: navigation, search

TODO possibly there should be per-operator pages.

This page describes some not implemented asynchronous control flow operators that could simplify creation of asynchronous application. The intial set of operators is based on experience of implementing the AsyncObjects framework and Sebyla project design.

Contents

Later

This is an extremely simple construct. But is useful when it is required to execute code in this or other vat at some later time. In AsyncObjects this constructs is implemented using AsyncAction class, and it was used quite a lot in applications.

	“later” ( “(” <vatHandle> “)” )? “->” “{“ 
		<code>
	“}”

When code is executed in other vat, the runtime checks that code closes only over deep frozen or pass by copy objects. If vat is not specified, the code is scheduled to be executed at later turn in this vat. The vatHandle is an expression that evaluates to capability of executing actions on the vat.

Using

This is a very simple construct that helps with resource management. The syntax is like the following:

	“using” “(“ ( (“def” <varName> “:=” )? <eventualOpenExpression> )+ “)” “->” “{“
		<statements>
	“}”

The operator is executed as the following:

  1. All resources are opened in the specified order. If expression returns promise, the next expression is not executed before first one complete. If <varName> is specified for resource, the resolved resource is assigned to it and it can be used in subsequent expressions and <statements>.
  2. If opening of some resource fails, the proposed result of operator is that failure.
  3. Statements are executed, the proposed result of operator is value of last expression. If the result is promise, the operator waits until it is resolved.
  4. All opened connections are closed, in reverse order. It includes connections that has not been named. If some close operations fail, the most top level one is returned.

Example:

using(def r1 := factory1<-openConnection(),  def r2 := factory2<-openConnection(), lock<-lockEnter()) -> {
	doSomething(r1,r1)   # assuming that lock is being held
}

The constructs expands to the following:

when(def r1 := factory1<-openConnection()) {
	when(def r2:= factory2<-openConnection()) {
		when( def tmpVar := lock<-lockEnter()) {
			doSomething(r1,r1); 	
		} finally {
			tmpVar<-close();			
		}
	} finally {
		r2<-close();
	}
} finally {
	r1<-close();
}

Seq

The are several form of seq operator. The basis form is the following.

	“seq” (“def” <varName> “:=”)? “{“
		<statements-1>
 	“}”  ( “then” (“def” <varName> “:=”)? “{“
		<statements-i>
 	“}” ) * ( “catch” “(“ <exceptionVar> “)” “{“ 
		<catchStatements>
	“}” )?  (“finally” “{“ 
		<finallyStatements> 
	“}”)?

The code is executed like the following:

  1. The code in <statements-i> is executed sequentially. The next code block is executed only if promise returned from previous code block is resolved. If the block yields near value, the next block is executed on the same turn.
  2. The result of operator is the value of the last block.
  3. If some of blocks fails, the next blocks are not executed, and control is passed to the catch section and the result of operator will be the result of executing catch statement. If there is no catchSection, the operator fails with expression.
  4. If there is a finally statement, it is executed after last block or after catch statement. If it fails, the entire seq expression fails with the same exception.

Example:

	seq def a := {
		doA()
	} then {
		doSomething(a)
	} catch(e) {
		print(e)
		throw e
	} finally {
		cleanup()
	}

Example is expanded as the following:

	when(someTempResolvedPromise) {
		def aVow := {
			doA()
		}
		when (def a := aVow) {
			doSomething(a)
		}
	}  catch(e) {
		print(e)
		throw e
	} finally {
		cleanup() 
	}

There is also a short form:

	“seq” “(“  (“def” <varName> “:=” )? <eventualExpression> ( “,” (“def” <varName> “:=” )? <eventualExpression> )* “)”

The comma separated expressions are executed in sequence. The named values are available at later expressions. The are three loops forms:

	“seq” “do” “{“
		<statements>
 	“}”  “while” “(“ <eventualExpressions> “)”

	“seq” “while” “(“ <eventualExpressions> “)” “{“
		<statements>
 	“}”  

	“seq” “for” <pattern> “in” <collection> “{“
		<statements>
 	“}”  

The semantics is the same as for normal loops. The next iteration starts only after previous completed. TODO it might be useful to keep intermediate results of previous iteration and to make them available in the body of the loop and conditions. On other hand, it is quite easy to use basic seq operator to achieve the same result. TODO “seq for” needs support for eventual iterators.

All

This operator allows execution of eventual expressions in parallel. The basic form is the following:

“all” “{“ 
	<expression>
“}” ( “and” “{“
	<expression>
“}” )+

The short form is the following:

“all” “(“ <expression> ( “,” <expression> )+ “)”

Semantics is the following:

  1. All expressions starts to be executed.
  2. If all expressions completed successfully, a list of results is returned.
  3. If at least one expression fails, an exception that contains generated results and failures is generated.

Example:

all {
	first()
} and {
	second()
} 

It is translated like the following:

{
	var wereFaults = false;
	var results = makeFlexList();
	results.setSize(2)
	var faults = makeFlexList();
	faults.setSize(2)
	def vow1 := seq def r: = {
		first()
	} then {
		results[0] := r
	} catch(e) {
		wereFaults = true
		faults[0] := e
		null
	}
	def vow2 := seq def r: = {
		second()
	} then {
		results[1] := r
	} catch(e) {
		wereFaults = true
		faults[1] := e
		null
	}
	when (def v1:=vow1, def v2:=vow2) {
		if(wereFaults) {
			throw makeAllFault(results,faults)
		} else {
			results.toConsList()
		}
	}
}

There is also a loop form that returns conslist of results.

“all” “for” <pattern> “in” <collection> “ “{“ 
	<expression>
“}” 

Any

Note: This is most tricky of the proposed operators. And I have not worked out details for E language yet. This construct is much simpler to do right in statically typed class based languages.

This operator allows execution of several eventual expressions in parallel and picking the first available result. The basic form is the following:

“any” (“suppress_faults”)? (“compensated”)? “{” 
	<expression>
“}” ( “or” (“compensated”)? “{”
	<expression>
“}” )+

The suppress faults modifier affects how faults are treated. If it presents, the faults are returned only if all alternatives returned failed. In this case the first received fault is thrown from any.

The compensated modifier specifies that the branch will return a near value that implements a Compensated interface:

interface Compensated {
    # this method is called by any operator to produce a value
    to run() : Vow[any]
    # this method is called by any operator if some branch has 
    # produced a result. Implementation that does nothing
    # is a valid implementation. The method gives an branch
    # a chance to cancel production of result. 
    to cancel()
    # If value from run method was produced but not returned 
    # from any operator, this method is invoked with produced
    # value as an argument.
    to compensateValue(value : any)
    # If run method failed with fault, the resulting fault
    # can be compensated as well if it is not used (for example
    # logged).
    to compensateFailure(fault)
}
any compensated {
  object _ {
    to run():Vow[any] {
      socketFactory<-connect("localhost", 2222)
    }
    to cancel() {}
    to compensateValue(socket) {
      print "eventually connected, but socket is closed"
      socket<-close()
    }
    to compensateFailure(e) {
      print e
    } 
  }
} or {
  # The method is assumed to throw timeout fault after specified
  # amount of milliseconds. Note that might have been a good idea
  # to implement cancel operation for timer task. Note that this
  # method always finishes with a fault. 
  timer<-timeoutMs(60000) 
} 

This is translated as the following:

later {
  def [promise, resolver] := makePromise()
  var branch1_finished = false
  var branch2_finished = false
  var c1 = object _ {
    to run():Vow[any] {
      socketFactory<-connect("localhost", 2222)
    }
    to cancel() {}
    to compensateValue(socket) {
      print "eventually connected, but socket is closed"
      socket<-close()
    }
    to compensateFailure(e) {
      print e
    } 
  }
  # run first branch
  seq def value := {
    c1.run()
  } then {
    branch1_finished := true
    if(!branch2_finished) {
       resolver<-resolve(value)
    } else {
       try {
         c.compensateValue(value)
       } catch(e) {log(e)}
    }
  } catch(e) {
    branch1_finished := true
    if(!branch2_finished) {
       resolver<-smash(e)
    } else {
       try {
         c.compensateFault(value)
       } catch(e) {log(e)}
    }
  }
  # run second branch
  seq def value := {
    timer<-timeout(60000)
  } then {
    # a dead branch in our case
    branch2_finished := true
    if(!branch1_finished) {
       resolver<-resolve(value)
       try {
         c1.cancel()
       } catch(e) {log(e)}
    }
  } catch(e) {
    branch2_finished := true
    if(!branch1_finished) {
       resolver<-smash(e)
       try {
         c1.cancel()
       } catch(e2) {log(e2)}
    }
  }
  promise
}

There is also a loop form that returns the first result.

“any” (“suppress_faults”)? “for” <pattern> “in” <collection> (“compensated”)? “{” 
	<expression>
“}” 
Personal tools
more tools