Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions content/data-sharing/mutable-and-sendable.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
---
title: "Mutable and Sendable"
section: "Data Sharing"
menu:
toc:
parent: "data-sharing"
weight: 40
---

## Problem

Suppose you have a central Supervisor actor that manages a very large data structure that contains the work produced by a collection of Worker actors. However, the Superviser must also respond to data requests (perhaps from the Workers themselves, or in this case, from another set of Requester actors). As a result, the Superviser's data structure must be both mutable (to update it in response to feedback from Workers) and sendable (to respond to requests from Requesters). `iso` is the only refcap that accommodates that, but our Supervisor can't use `iso` here because it must keep a reference to the data structure to continue to respond to incoming updates and requests. How to proceed?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

" iso is the only refcap that accommodates that"

what "that" refers to is unclear.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you have a really good introduction to the problem here, but you should let it breathe. This is a rather complicated scenario and I think you should look to expand the explanation of the problem quite a lot. Like 3-4x more content to dig in more seems completely reasonable (if not more).

I don't think I would understand the problem were it not for working through a solution with you in Zulip.


## Solution

```pony
use mut = "collections"
use "persistent/collections"

type Datum is List[I32]


Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can drop the extra newline here.

actor Worker
let _supervisor: Supervisor

new create(supervisor: Supervisor) =>
_supervisor = supervisor

be do_work(id: USize) =>
let result = [as I32: 1; 2; 3; 4] // initial state array
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on small screens, this side comment is hard to deal with, can you move it above the let result. The same comment would apply elsewhere.

// do heavy lifting here; mutate array as needed ..
// then send it to supervisor's update behavior as
// a List val
_supervisor.update(id, Lists[I32](result))


actor Requester
let _supervisor: Supervisor

new create(supervisor: Supervisor) =>
_supervisor = Supervisor

be request(id:USize) =>
_supervisor.get(id, this)

be receive(id: USize, result: Datum) =>
// this behavior is called by Supervisor in response to a request
// use the result here


actor Supervisor
let _data: mut.Map[USize, Datum]
let _default: Datum = Lists[I32]([0; 0; 0; 0])

new create() =>
// preallocate 2 ** 28 (or more) elements
_data = mut.Map[USize, Datum](268_435_456)

be update(id: USize, new_data: Datum) =>
// may want _data.upsert here instead, depending on application
_data.update(id, new_data)

be get(id: USize, requester: Requester) =>
requester.receive(id, _data.get_or_else(id, _default))
```


The Supervisor uses a mutable `ref` Map collection of persistent immutable `val` Lists. Because the Map is mutable, new data can be easily added or updated without the copying costs required by a persistent Map. Because we're storing the individual work product (Datum) in persistent immutable Lists, they are refcap `val` which accommodates sharing with Requesters.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels rather tacked on after the solution. This suggest to me that it should be part of a discussion or it needs to be expanded. I'm leaning towards, only code in solution and then do a breakdown of the different bits of code and what they are doing in the discussion.

I'm imagining something like how the solution is broken down in the waiting pattern. https://patterns.ponylang.io/async/waiting.html

I think that breakdown should be part of the solution which leaves you free to cover in Discussion how it relates to the other patterns you reference there.



## Discussion

The pattern presented here builds on the [persistent data structures pattern](/data-sharing/persistent-data-structures.html). The mutable map is cheaply updated by the Supervisor, yet the results are ultimately stored in persistent `Lists`, which are `val` and can be shared with other actors.

Several other solutions might occur to you before the one presented here. Such as:

Could we use the [copying pattern](/data-sharing/copying.html) instead? If the data structure was fully mutable (say a Map of Arrays), we would have to copy the `ref` Array to a `val` Array on every update and on every request. The solution presented here only copies on update, which is a big win, especially if requests materially outnumber updates.

Persistent Maps are sendable and can be updated. Can we just use that? Our data structure could be so large that the copying cost of each update to a persistent structure seems prohibitive.

Could we use a `Map iso` data structure? Well, if we chose an `iso`, the compiler would allow us to both mutate it for updates and send it to a Requester. However, if we send it to a Requester, the Supervisor no longer has it, and it blocks! The supervisor can no longer continue to process updates and requests until the `iso` is sent back by the Requester. The blocking is undesirable, and "sending the iso back" requires additional code.