---
title: "nanonext - Scalability Protocols"
vignette: >
  %\VignetteIndexEntry{nanonext - Scalability Protocols}
  %\VignetteEngine{litedown::vignette}
  %\VignetteEncoding{UTF-8}
---




``` r
library(nanonext)
```

### 1. Request Reply Protocol

`nanonext` implements remote procedure calls (RPC) using NNG's req/rep protocol for distributed computing. Use this for computationally-expensive calculations or I/O-bound operations in separate server processes.

**[S] Server process:** `reply()` waits for a message, applies a function, and sends back the result. Started in a background 'mirai' process.


``` r
m <- mirai::mirai({
  library(nanonext)
  rep <- socket("rep", listen = "tcp://127.0.0.1:6556")
  reply(context(rep), execute = rnorm, send_mode = "raw")
  Sys.sleep(2) # linger period to flush system socket send
})

```

**[C] Client process:** `request()` performs async send/receive, returning immediately with a `recvAio` object.


``` r
req <- socket("req", dial = "tcp://127.0.0.1:6556")
aio <- request(context(req), data = 1e8, recv_mode = "double")

```
The client can now run additional code while the server processes the request.


``` r
# do more...
```

When the result is needed, call the recvAio using `call_aio()` to retrieve the value at `$data`.


``` r
call_aio(aio)$data |> str()
#>  num [1:100000000] -0.75 -0.524 0.122 1.186 1.651 ...
```

Since `call_aio()` blocks, alternatively query `aio$data` directly, which returns 'unresolved' (logical NA) if incomplete.

For server-side operations (e.g., writing to disk), calling or querying the value confirms completion and provides the function's return value (typically NULL or an exit code).

The [`mirai`](https://doi.org/10.5281/zenodo.7912722) package (<https://mirai.r-lib.org/>) uses `nanonext` as the back-end to provide asynchronous execution of arbitrary R code using the RPC model.

### 2. Publisher Subscriber Protocol

`nanonext` implements NNG's pub/sub protocol. Subscribers can subscribe to one or multiple topics broadcast by a publisher.


``` r
pub <- socket("pub", listen = "inproc://nanobroadcast")
sub <- socket("sub", dial = "inproc://nanobroadcast")

sub |> subscribe(topic = "examples")

pub |> send(c("examples", "this is an example"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples"           "this is an example"

pub |> send("examples at the start of a single text message", mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples at the start of a single text message"

pub |> send(c("other", "this other topic will not be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> 'errorValue' int 8 | Try again

# specify NULL to subscribe to ALL topics
sub |> subscribe(topic = NULL)
pub |> send(c("newTopic", "this is a new topic"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> [1] "newTopic"            "this is a new topic"

sub |> unsubscribe(topic = NULL)
pub |> send(c("newTopic", "this topic will now not be received"), mode = "raw")
#> [1] 0
sub |> recv("character")
#> 'errorValue' int 8 | Try again

# however the topics explicitly subscribed to are still received
pub |> send(c("examples will still be received"), mode = "raw")
#> [1] 0
sub |> recv(mode = "character")
#> [1] "examples will still be received"
```

The subscribed topic can be of any atomic type (not just character), allowing integer, double, logical, complex and raw vectors to be sent and received.


``` r
sub |> subscribe(topic = 1)
pub |> send(c(1, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> [1]  1 10 10 20
pub |> send(c(2, 10, 10, 20), mode = "raw")
#> [1] 0
sub |> recv(mode = "double")
#> 'errorValue' int 8 | Try again

close(pub)
close(sub)

```

### 3. Surveyor Respondent Protocol

Useful for service discovery and similar applications. A surveyor broadcasts a survey to all respondents, who may reply within a timeout period. Late responses are discarded.


``` r
sur <- socket("surveyor", listen = "inproc://nanoservice")
res1 <- socket("respondent", dial = "inproc://nanoservice")
res2 <- socket("respondent", dial = "inproc://nanoservice")

# sur sets a survey timeout, applying to this and subsequent surveys
sur |> survey_time(value = 500)

# sur sends a message and then requests 2 async receives
sur |> send("service check")
#> [1] 0
aio1 <- sur |> recv_aio()
aio2 <- sur |> recv_aio()

# res1 receives the message and replies using an aio send function
res1 |> recv()
#> [1] "service check"
res1 |> send_aio("res1")

# res2 receives the message but fails to reply
res2 |> recv()
#> [1] "service check"

# checking the aio - only the first will have resolved
aio1$data
#> [1] "res1"
aio2$data
#> 'unresolved' logi NA

# after the survey expires, the second resolves into a timeout error
msleep(500)
aio2$data
#> 'errorValue' int 5 | Timed out

close(sur)
close(res1)
close(res2)

```

`msleep()` is an uninterruptible sleep function (using NNG) that takes a time in milliseconds.

The final value resolves to a timeout error (integer 5 classed as 'errorValue'). All error codes are classed as 'errorValue' for easy distinction from integer message values.

### 4. Device (Broker / Proxy)

A device is a zero-copy forwarder that relays raw messages between two Sockets, providing the building block for brokers and proxies. `device_aio()` runs the device asynchronously on background threads, returning immediately.

The two Sockets must be opened in raw mode and use complementary protocols. Here a pair of raw 'pair' Sockets bridges two endpoints: a message arriving on one side is relayed out of the other.


``` r
front <- socket("pair", listen = "inproc://frontend", raw = TRUE)
back <- socket("pair", listen = "inproc://backend", raw = TRUE)

# start the forwarder; it runs in the background until stopped
d <- device_aio(front, back)

# a client on each side of the bridge
c1 <- socket("pair", dial = "inproc://frontend")
c2 <- socket("pair", dial = "inproc://backend")

# a message sent by c1 is relayed through the device and received by c2
c1 |> send("relayed via the device", mode = "raw")
#> [1] 0
c2 |> recv(mode = "character", block = 500)
#> [1] "relayed via the device"

close(c1)
close(c2)

```

While the device runs, its `$result` is 'unresolved' (logical NA). Stop it with `stop_aio()`, or by closing either Socket, and the result resolves to an 'errorValue'.


``` r
d$result
#> 'unresolved' logi NA

stop_aio(d)
d$result
#> 'errorValue' int 20 | Operation canceled

close(front)
close(back)

```
