Eider Documentation Release 1.0.0

Bart Robinson

Oct 09, 2019

Contents

1 Installation 3 1.1 Python...... 3 1.2 JavaScript...... 3 1.3 ++...... 4

2 Getting Started 5 2.1 Python...... 5 2.2 JavaScript...... 6 2.3 C++...... 7

3 Contents 9 3.1 Justification...... 9 3.2 Concepts...... 10 3.3 Protocol...... 13 3.4 Conventions...... 18

4 Blame 23

Index 25

i ii Eider Documentation, Release 1.0.0

Eider is an object-oriented, asynchronous, late-binding, web-first, polyglot RPC protocol. Eider is also the name of the reference implementations in Python and JavaScript. Out of the box, Eider uses WebSockets for transport and JSON or MessagePack for serialization. The core protocol and concepts are transport- and format-neutral. With Eider, developing applications that combine server-side, client-side, and in-browser code is duck soup!

Contents 1 Eider Documentation, Release 1.0.0

2 Contents CHAPTER 1

Installation

1.1 Python

Works in CPython 3.4+ and PyPy3. Requires either aiohttp or websockets. Includes elective support for MessagePack using msgpack-python, if available. pip install eider

# either: pip install aiohttp # recommended pip install websockets # slower

# optional: pip install msgpack

You can also check out the source code on GitHub.

1.2 JavaScript

Works in Node.js 6+, modern browsers, and any other environment that supports ES6. For clients, no external libraries are strictly required. Node.js servers need the ws package. Other optional dependen- cies are msgpack-lite for MessagePack encoding and weak for implicit remote garbage collection. npm install eider-rpc

# for servers: npm install ws

# optional: npm install msgpack-lite npm install weak

3 Eider Documentation, Release 1.0.0

For the browser: eider-rpc.min.js. You can also check out the source code on GitHub.

1.3 C++

The eider-pybind11 project provides a simple header file that can enable classes implemented in C++ to be served over Eider connections using pybind11.

4 Chapter 1. Installation CHAPTER 2

Getting Started

2.1 Python

Here is a simple server (simple_server.py):

1 import eider

2

3 class DuckTester(eider.LocalRoot):

4

5 def is_it_a_duck(self, obj):

6 return (

7 obj['looks'] =='like a duck' and

8 obj['swims'] =='like a duck' and

9 obj['quacks'] =='like a duck')

10

11 eider.serve(12345, root=DuckTester)

And here is a corresponding client (simple_client.py):

1 import eider

2

3 how='like a duck'

4 obj={'looks': how,'swims': how,'quacks': how}

5

6 with eider.BlockingConnection('ws://localhost:12345') as conn:

7 with conn.create_session() as duck_tester:

8 is_duck= duck_tester.is_it_a_duck(obj)

9 print("It's probably"+("" if is_duck else "NOT")+"a duck.")

And here is an equivalent client using non-blocking APIs: (simple_client_async.py):

1 import asyncio, eider

2

3 how='like a duck' (continues on next page)

5 Eider Documentation, Release 1.0.0

(continued from previous page)

4 obj={'looks': how,'swims': how,'quacks': how}

5

6 async def ducktest():

7 async with eider.Connection('ws://localhost:12345') as conn:

8 async with conn.create_session() as duck_tester:

9 is_duck= await duck_tester.is_it_a_duck(obj)

10 print("It's probably"+("" if is_duck else "NOT")+"a duck.")

11

12 asyncio.run(ducktest())

2.2 JavaScript

Here is an equivalent server in JavaScript (simple_server.js):

1 const Eider= require('eider-rpc');

2

3 class DuckTester extends Eider.LocalRoot {

4 is_it_a_duck(obj) {

5 return obj.looks === 'like a duck'&&

6 obj.swims === 'like a duck'&&

7 obj.quacks === 'like a duck';

8 }

9 }

10

11 Eider.serve(12345, {root: DuckTester});

And here is an equivalent client (simple_client.html):

1

2

And here is an equivalent client using async/await syntax (simple_client_async.html):

1

2

2.3 C++

Here is how the core of the Python server could be written in C++ (simple_server.cpp):

1 #include

2

3 using namespace eider_pybind11;

4

5 struct DuckTester : LocalRoot {

6 using LocalRoot::LocalRoot;

7

8 bool is_it_a_duck(py::object obj) {

9 return std::string(py::str(obj["looks"]))=="like a duck"&&

10 std::string(py::str(obj["swims"]))=="like a duck"&&

11 std::string(py::str(obj["quacks"]))=="like a duck";

12 }

13 };

14

15 PYBIND11_MODULE(ducktest, m) {

16 bind(m);

17

18 py::class_(m,"DuckTester")

19 .def(py::init())

20 .def("is_it_a_duck",&DuckTester::is_it_a_duck);

21 }

2.3. C++ 7 Eider Documentation, Release 1.0.0

8 Chapter 2. Getting Started CHAPTER 3

Contents

3.1 Justification

(a.k.a. Yet another RPC protocol?) There are many mechanisms out there. Some of them are very nice. Eider was developed because, as far as the author could tell, no single one of them meets all five of these criteria: • Object-oriented. Many RPC protocols only allow you to call a flat list of exposed functions. Eider lets you work with remote objects and their exposed methods. • Asynchronous. Many RPC protocols require that responses be returned in the same order as requests were sent. Eider allows responses to be returned in any order, using asynchronous fulfillment mechanisms such as Promises (in JavaScript) and Futures (in Python). • Late-binding. Many RPC protocols require invokable function signatures to be declared ahead of time in some special way. Eider has no such requirement; instead it follows the Python tradition of duck typing (if it looks like a duck, swims like a duck, and quacks like a duck. . . it’s probably a duck). There is no interface description language; exposed objects and methods are coded using the natural syntax of their native language. Type mismatches are reported via exceptions at runtime.1 • Web-first. Many RPC protocols use direct TCP or pipe connections and rely on custom binary formats or overly-verbose XML, all of which make them difficult-to-impossible to use from JavaScript running inside the browser. Eider uses web standards like WebSockets and JSON, so client-side web apps can be first-class participants in an Eider-based system. • Polyglot. Many RPC protocols are tied to a single programming language. Eider is language-agnostic. Eider also has these nice features: • Lightweight. The Python and JavaScript implementations are just a few kilobytes each and have minimal external dependencies. Transmitted messages are nearly as compact as in JSON-RPC.

1 It is important not to confuse late binding with weak typing. Eider methods are precisely as strongly typed as the language used to implement them. To assist with coding discipline, Eider has built-in support for function annotation, interactive help, and runtime object inspection.

9 Eider Documentation, Release 1.0.0

• Natural syntax. Syntactic sugar is used to make calling remote methods and accessing remote properties as natural as possible. The Python implementation also provides blocking versions of client functions, so simple clients can avoid the complexity of Futures and callbacks. • Peer-to-peer. After the initial connection, there is no functional difference between client and server applica- tions. Each peer can be both a provider and consumer of remote interfaces. Callbacks are even supported, where a locally-provided object or method is passed to a remote interface. • Built-in proxying. Any Eider peer can act as a bridge between any two of its peers, allowing them to call each other’s methods without connecting directly. • Cancellation. Eider consumers can request the graceful cancellation of long-running remote method calls without terminating the connection or losing session state.

3.2 Concepts

Note: For simplicity, in this document we will prefer the Python blocking (synchronous) API. The asynchronous JavaScript and Python APIs are very similar.

3.2.1 Outline

Typical Eider communication follows this basic pattern: 1. Establish a connection. 2. Create a remote session to contain object references. 3. Call a method on the root object of the session. 4. Call any other desired methods on the root object or on returned objects. 5. Close session(s) to reclaim memory. 6. Repeat 2-5 as desired. 7. (concurrently with 2-6) Execute any incoming method calls requested by the connection peer. 8. Close the connection. Steps 2-7 are entirely optional; remember, every Eider peer can be a consumer of remote APIs (i.e. a client), a provider of such APIs (i.e. a server), or both. To illustrate these steps, here is an example of a client:

>>> from eider import BlockingConnection >>> conn= BlockingConnection('ws://localhost:12345/') # (1) >>> sess= conn.create_session() # (2) >>> oven= sess.root().new_Oven() # (3) >>> oven.cook('goose') # (4) 'your goose is cooked!' >>> sess.close() # (5) >>> conn.close() # (8)

And here is a corresponding server:

10 Chapter 3. Contents Eider Documentation, Release 1.0.0

import eider

class Oven(eider.LocalObject): def cook(self, thing): return 'your'+ thing+' is cooked!' # (7)

class OvenFactory(eider.LocalRoot): _newables= [Oven]

eider.serve(12345, root=OvenFactory)

3.2.2 Connection

A Connection (or BlockingConnection) object wraps a single WebSocket connection and handles the details of parsing incoming messages and dispatching them to the appropriate handlers. The only things that can be done with a Connection object after it is created are to create new sessions (local or remote), and to close the connection. Closing the connection also closes any related sessions.

3.2.3 Session

A LocalSession is a container for objects that may be remotely accessed. A RemoteSession is a reference to a LocalSession provided by another peer. Typically, sessions are created by clients via Connection.create_session(), but sessions may also be spontaneously created by servers via Connection.create_local_session(). A BridgedSession is a special kind of RemoteSession that allows a peer A to access another peer C through an intermediate peer B. The bridge is created by the peer B by creating a Bridge object (typically in response to a request by A) and passing it back to A where it will be unmarshalled into a BridgedSession. The session’s primary purpose is to serve as a garbage collection mechanism. By maintaining a collection of all objects reachable by a client, the server can release resources when the client no longer needs them (either because the connection was dropped, or the session was explicitly closed). This also relieves most clients of the burden of having to explicitly release object references, while allowing them to maintain a long-lived connection.

3.2.4 Root Object

Every session starts out with exactly one root object which serves as the origin for all further use of the session. In the above example, the root object is an instance of the OvenFactory class. In addition to providing its own methods, the root object may allow new objects to be created. In the above example, the special _newables class member specifies classes for which the Eider machinery will automatically create new_Foo() factory methods on the root object. The root object can also explicitly define methods that return new objects. The root object’s lifetime coincides with that of its session. When the root object is released, its session is closed, and vice versa.

3.2.5 Objects

A LocalObject is a server-side object that lives within a LocalSession and may be remotely accessed. A RemoteObject is a client-side reference to a LocalObject provided by another peer. Objects are reference-

3.2. Concepts 11 Eider Documentation, Release 1.0.0 counted, may be explicitly released, and are implicitly cleaned up when the session in which they were created is closed. Object class definitions follow the normal syntax, semantics, and conventions of their host languages, with one impor- tant proviso: any property or method that begins with an underscore (_) will be not be remotely accessible. This rule allows objects to protect private internal data from remote clients. Accessors may be defined to give access to internal data. Every object inherits a few basic methods: LocalObject.addref() Increment the object’s reference count. It should almost never be necessary to explicitly call this method. LocalObject.release() Decrement the object’s reference count. It should almost never be necessary to explicitly call this method. LocalObject.help() LocalObject..help() Get documentation for the object or one of its methods. In Python, this returns the docstring; in JavaScript, it returns the class’s or method’s help property, if any. LocalObject.dir() Get a list of names of the object’s methods. LocalObject.taxa() Get a list of names of the object’s base classes. LocalObject..signature() Get the type signature of a method. This uses PEP 484-style type hints in Python. The JavaScript implementation only returns basic information. Instances of RemoteObject, in addition to allowing the methods of the referenced object to be called, have this local method: RemoteObject._close() Release the object without waiting for garbage collection. This guards against double-releasing and gracefully handles dropped connections. This should normally be called instead of directly calling release(). De- spite the leading underscore in the name, client code may call this function. The underscore merely exists to differentiate this from a remote method. Both LocalObject and RemoteObject also support the context manager protocol, so they can be used in the with statement in Python and Eider.using() in JavaScript. (In Python 3.5+, async with should be used for improved behavior.) In environments where finalizers are available (e.g. Python, Node.js with the weak package), RemoteObject. _close() will be automatically called when the RemoteObject is garbage-collected. In other environments (e.g. standard JavaScript in the browser), if a RemoteObject becomes unreachable without _close() having been called, a remote resource leak may occur until the corresponding remote session is closed.

3.2.6 Native Objects

Every connection includes a built-in NativeSession object (which uses the reserved lsid of -1). This session is useful for marshalling “native” functions and objects (objects which do not inherit from LocalObject). The ability to pass native objects to Eider APIs can simplify client code, because callback functions do not have to be housed within LocalObject definitions. However, native objects do not benefit from the reference-counting and automatic session cleanup that LocalObject provides. Every time a native object is marshalled, a new reference to it is created that will survive for the life of the connection unless the remote peer closes the corresponding RemoteObject. This could result in local memory leaks if callbacks are passed many times over the same connection.

12 Chapter 3. Contents Eider Documentation, Release 1.0.0

3.2.7 Method Calls

In the Python blocking API (where BlockingConnection, BlockingSession, and BlockingObject are substituted for Connection, RemoteSession, and RemoteObject), remote method calls block until a value is returned or an exception is raised. In the asynchronous APIs, each method call returns a Future (Python) or Promise (JavaScript) representing the eventual result or exception. These objects are equipped with a cancel() method that can be used to send a cancellation request for the call.

3.3 Protocol

The Eider protocol starts with the JSON-RPC 1.0 specification and extends it to manage sessions, bridged connections, object and method marshalling, call cancellation, and alternative serialization formats.

3.3.1 Messages

Request

A request (method invocation) takes the following form:

{ "dst":1, "src":2, "id":3, "this":{ "__*__":4, "rsid":5 }, "method": "answer", "params":[ "life", "the universe", "everything" ] } dst For calls that should be forwarded (bridged) to another peer, this is an integer identifying the destination con- nection. For direct calls, it should be null or not present. src For calls that have been forwarded (bridged) from another peer, this is an integer identifying the source connec- tion. For direct calls, it should be null or not present. id This is an integer uniquely identifying the request. It may also be null or not present, in which case no response will be returned. this A reference to the object whose method is to be invoked (see object marshalling). For calls to the LocalSessionManager (e.g. open() and free()), this may be null or not present. method A string identifying the method to be invoked.

3.3. Protocol 13 Eider Documentation, Release 1.0.0

params An array of arguments to pass to the method. May be not present if there are no arguments.

Response

A response (method return) takes the following form:

{ "dst":2, "id":3, "result": 42 }

dst For responses to forwarded (bridged) calls, this is an integer identifying the origin of the call (the src of the request becomes the dst of the response). For direct responses, this should be null or not present. id An integer identifying the request to which this response corresponds. result The return value of the call, or null if the method did not return a value. If this property is missing, Eider will interpret the message as an error response.

Error Response

If a method throws an exception, the response takes the following form:

{ "dst":2, "id":3, "error":{ "name": "TerribleGhastlyError", "message": "Don't Panic" } }

dst This has the same meaning as for successful responses. id This has the same meaning as for successful responses. error This is an object representing the thrown exception. At minimum, it should have name and message string properties describing the type of error and any pertinent details. It may also have a stack string property with a stack trace (the format of which is implementation-specific). Eider implementations may attempt to use the name field to convert the exception to an appropriate native exception type before passing it to client code. They may also use the stack field as appropriate to simulate exception chaining.

Cancellation Request

A request to cancel (i.e. abort) an outstanding method call takes the following form:

14 Chapter 3. Contents Eider Documentation, Release 1.0.0

{ "dst":1, "src":2, "cancel":3 }

dst This has the same meaning as for method call requests. src This has the same meaning as for method call requests. cancel This is an integer identifying the request which the caller wishes to cancel. Callees are not required to honor cancellation requests; they may still finish the call and return a result or an error. However, such results and errors will be ignored by the caller. There is no mechanism to acknowledge a cancellation request; after sending it, the caller should not assume any specific remote state was reached. The Future or Promise representing the remote call will have its exception immediately set to asyncio.CancelledError (Python) or Eider.Errors.CancelledError (JavaScript).

3.3.2 Serialization Formats

By default, Eider expects all text messages to be encoded in JSON and all binary messages in MessagePack. The reference implementations also allow alternative format(s) to be specified when creating a Connection object; however, the particular format(s) to be used must be either agreed upon in advance or transmitted through some side- channel. Eider also includes a mechanism for specifying an alternative format on a per-message basis. To do this, the message must be split into two parts: a header formatted in JSON (or the agreed-upon format, as above), and an arbitrarily- formatted body. These parts must be sent as separate WebSocket messages, one immediately after the other. To distinguish a message header from a complete message, and to specify the format used for the subsequent body, the message header object must contain a format field. This field should be a string identifying a serialization format that the remote peer knows how to handle. The string "" should be reserved for JSON and "msgpack" for MessagePack. When the format field is present, the only other fields that the header message should contain are dst, src, id, and method. The this, params, result, and error fields are expected to be contained in the body message instead. For example, the request above could be transmitted as these two messages:

{ "dst":1, "src":2, "id":3, "method": "answer", "format": "json" }

{ "this":{ "__*__":4, "rsid":5 }, "params":[ "life", (continues on next page)

3.3. Protocol 15 Eider Documentation, Release 1.0.0

(continued from previous page) "the universe", "everything" ] }

And the response could be transmitted as these two messages:

{ "dst":2, "id":3, "format": "json" }

{ "result": 42 }

If the method throws an exception, the response could be:

{ "dst":2, "id":3, "format": "json" }

{ "error":{ "name": "TerribleGhastlyError", "message": "Don't Panic" } }

Separating the header and body in this way yields an important benefit for calls over a bridged session. Because all the information needed to forward messages between two peers (i.e., dst and src) is contained within the header, the bridging peer does not have to decode and re-encode the contents of the message body when relaying a message. Because it would quickly become tedious to have to specify the format for every method call, the Connection. create_session() method allows you to specify an lformat and rformat to be used for all method calls and responses for objects in a given session. The lformat specifies how outgoing messages will be encoded, and the rformat is passed to the remote peer to request how to encode its responses.

3.3.3 Marshalling References

In addition to “plain old data” (strings, numbers, null, arrays/lists, objects/dictionaries), the this, params, and result fields of requests and responses may contain references to objects, bound methods, and bridged sessions. References are represented as objects (dictionaries) containing a property named "__*__", known as the object-id. The root object of each session has null as its object-id. For all other objects, the object-id is an integer uniquely identifying it within its session. The way references are encoded depends on the chosen serialization format. For JSON, they are simply encoded “in- band” using the above representation. For MessagePack, the representation is encoded and then wrapped in extension type 0. This extra level of indirection makes MessagePack a safer choice if the data is coming from an unknown source, because it eliminates the possibility of the "__*__" key colliding with plain old data.

16 Chapter 3. Contents Eider Documentation, Release 1.0.0

Warning: When using JSON serialization, it is important to make sure that plain data objects passed through Eider do not contain properties named "__*__", as this may confuse the marshalling layer. If this cannot be guaranteed, then use MessagePack or another serialization format that provides a way to distinguish between data and object references.

The Eider implementations handle the details of marshalling (encoding) and unmarshalling (decoding) object refer- ences into and out of this representation.

Remote Objects

Objects residing on the remote peer (such as this for a method call) are represented like this:

{ "__*__":1, "rsid":2 }

Here, rsid is an integer uniquely identifying the remote session to which the object belongs.

Local Objects

Similarly, objects residing on the local peer (such as the result of a new_* call, or a local reference passed for use as a callback) are represented like this:

{ "__*__":1, "lsid":2 } where lsid is an integer uniquely identifying the local session to which the object belongs.

Bound Methods

References to bound methods of local and remote objects may also be included in Eider messages. The representation of the frobnicate method of a remote object with object-id of 1 in remote session 2 would look like this:

{ "__*__":1, "rsid":2, "method": "frobnicate" }

Change rsid to lsid to refer to a method of a local object instead.

Bridged Sessions

When a peer B creates a bridged session between peers A and C, it is passed back to peer A using this representation:

{ "__*__":1, "lsid":2, (continues on next page)

3.3. Protocol 17 Eider Documentation, Release 1.0.0

(continued from previous page) "bridge":{ "dst":3, "rsid":4, "lformat": "json" } }

The object-id and lsid fields identify the bridge object on peer B, used to manage the lifetime of the bridge. Within the bridge field, the dst field identifies peer C, the rsid field identifies the remote session on peer C, and lformat is the requested serialization format for peer A to use when making calls or responding to callbacks.

3.3.4 Session Management

When a connection is first established, there are no remote sessions yet, and therefore no remote objects with methods to call. With no methods to call, how do you create a remote session? The answer is that every Eider connection provides a special session (with lsid of null) whose root object provides a special method: LocalSessionManager.open(lsid, lformat=None) Create a new session which may be subsequently identified with lsid. Method call responses and callbacks originating from this session will be encoded using the requested lformat. It should not be necessary to call this method directly; Connection.create_session() will handle this for you. A remote session is closed when its root object is released. Again, this should not be done directly, but rather by call- ing RemoteSession.close() or using the session in a with statement (Python 3.4), async with statement (Python 3.5+), or Eider.using() (JavaScript).

3.3.5 Native Objects

The null session also provides a method that becomes important when passing native objects and functions: LocalSessionManager.free(lsid, loid) Release the specified object. For instances of LocalObject, this is has the same effect as calling release(). For native objects, which do not participate in Eider’s reference-counting protocol, this deletes the connection’s internal reference to the object. This method is called internally by RemoteObject._close() to mask the difference between LocalObject instances and native objects.

3.4 Conventions

To make developing with Eider easier, the reference implementations include support for some API conventions. While the use of these conventions is highly recommended, they are not part of the core Eider protocol.

3.4.1 Data Members

Simple data properties on objects may be simulated with getter and setter methods. A property named foo should have a getter named foo() and a setter named set_foo(). If these methods are not explicitly defined, Eider will attempt to access the corresponding property directly when the methods are called. For example:

18 Chapter 3. Contents Eider Documentation, Release 1.0.0

class Circle(eider.LocalObject):

def __init__(self, lsession, center, radius): super().__init__(lsession)

# private data members begin with _ self._center= center

# public data member (with implicit getter/setter) self.radius= radius

# explicit getter def center(self): return self._center

# explicit setter def set_center(self, center): self._center= center

# explicit getter for a computed property def diameter(self): return self.radius * 2

# explicit setter for a computed property def set_diameter(self, diameter): self.radius= diameter/2

The Python blocking API includes syntactic sugar to take advantage of this convention:

>>> circle= root.new_Circle((1,2),3) >>> circle.center() [1, 2] >>> circle.radius=4 # equivalent to circle.set_radius(4) >>> circle.radius() # note the required parens 4 >>> circle.diameter() 8 >>> circle.color='blue' # dynamically add an attribute >>> circle.color() 'blue'

3.4.2 Length

Objects which have some concept of size or length may report this via a length() method: class Stick(eider.LocalObject):

def __init__(self, lsession, length): super().__init__(lsession) self._length= length

def length(self): return self._length

The Python blocking API calls this method when len() is applied:

3.4. Conventions 19 Eider Documentation, Release 1.0.0

>>> stick= root.new_Stick(42) >>> len(stick) # equivalent to stick.length() 42

3.4.3 Accessing Elements

Objects which have some concept of subscripting, indexing, or element access may expose this functionality using methods named get(), set() and/or remove(): class CornedBeefHash(eider.LocalObject):

def __init__(self, lsession): super().__init__(lsession) self._recipe={}

def get(self, ingredient): return self._recipe[ingredient]

def set(self, ingredient, amount): self._recipe[ingredient]= amount

def remove(self, ingredient): del self._recipe[ingredient]

Once again, the Python blocking API converts these to native syntax:

>>> hash= root.new_CornedBeefHash() >>> hash['potato']='6 oz' # equivalent to hash.set('potato', '6 oz') >>> hash['potato'] # equivalent to hash.get('potato') '6 oz' >>> del hash['potato'] # equivalent to hash.remove('potato')

3.4.4 Iterator Protocol

Objects which support the concept of iteration may expose an iter() method which returns an iterator with iter() and next() methods. This pattern is inspired by the iterator protocols of JavaScript and Python (though it is more similar to the JavaScript protocol, in that next() always returns an object, rather than throwing an exception to indicate completion). Here is an example where the iterable object is its own iterator: class Fibonacci(eider.LocalObject): """Iterable that yields the first n Fibonacci numbers."""

def __init__(self, lsession, n): super().__init__(lsession) self._n=n self._f0=1 self._f1=1

def iter(self): return self

def next(self): if self._n<=0: return {'done': True} (continues on next page)

20 Chapter 3. Contents Eider Documentation, Release 1.0.0

(continued from previous page) self._n-=1 f0= self._f0 self._f0= self._f1 self._f1+= f0 return {'value': f0}

This iterable can be used with the Python blocking API:

>>> fib= root.new_Fibonacci(5) >>> for f in fib: ... print(f) ... 1 1 2 3 5

An asynchronous version is also possible:

>>> async def print_number(f): ... print(f) ... >>> async def print_fibs(n): ... async with (await vanth.new_Fibonacci(5)) as fib: ... await eider.async_for(fib, print_number) ... >>> asyncio.get_event_loop().run_until_complete(print_fibs(5)) 1 1 2 3 5

Warning: The async for statement in Python 3.5+ may also be used with Eider iterables, but the eider. async_for() function is recommended instead because it ensures deterministic cleanup of the remote iterator object. See PEP 533 for more information.

JavaScript usage:

await Eider.using(root.new_Fibonacci(5), async fib => await Eider.forAwait(fib, async f => { console.log(f); }) );

Warning: The for await. . . of statement may also be used with Eider iterables, but it suffers from the same shortcomings as the async for statement in Python (see above note). The provided Eider.forAwait() function is the recommended alternative.

3.4. Conventions 21 Eider Documentation, Release 1.0.0

3.4.5 Sequence Protocol

If an object does not provide an iter() method, it may still support iteration by providing a get() method that takes integers increasing from zero and throws IndexError (available in JavaScript as Eider.Errors.IndexError) when the collection is exhausted. For example: class Range(eider.LocalObject):

def __init__(self, lsession, start, stop, step): super().__init__(lsession) self._start= start self._stop= stop self._step= step

def get(self, i): n= self._start+i * self._step if not (self._start<=n< self._stop): raise IndexError return n

Blocking Python client:

>>> r= root.new_Range(37, 49,3) >>> for n in r: ... print(n) ... 37 40 43 46

Asynchronous Python 3.5+ client:

>>> async def print_range(start, stop, step): ... r= await root.new_Range(start, stop, step) ... async for n in r: ... print(n) ... >>> asyncio.get_event_loop().run_until_complete(print_range(37, 49,3)) 37 40 43 46

JavaScript: await Eider.using(root.new_Range(37, 49,3), async r => { for await (n of r) { console.log(n); } });

Note: When iterating using the sequence protocol instead of the iterator protocol, it is safe to use the async for and for await...of statements, because no implicit remote iterator object is created.

22 Chapter 3. Contents CHAPTER 4

Blame

Eider began as an internal software library at ON Semiconductor and was open-sourced under the Apache License 2.0 in April 2017. Bart Robinson is the original author and current maintainer. King Eider photo by Ron Knight(CC BY 2.0).

23 Eider Documentation, Release 1.0.0

24 Chapter 4. Blame Index

Symbols _close() (RemoteObject method), 12 A addref() (LocalObject method), 12 dir() (LocalObject method), 12 F free() (LocalSessionManager method), 18 H help() (LocalObject method), 12 O open() (LocalSessionManager method), 18 R release() (LocalObject method), 12 T taxa() (LocalObject method), 12

25