Skip to content

Commit 38fa178

Browse files
authored
Raise RPC limit (#21856)
* Raise RPC limit * Fix TypeScriptExample
1 parent b5d99ea commit 38fa178

File tree

3 files changed

+172
-96
lines changed

3 files changed

+172
-96
lines changed

src/content/docs/workers/runtime-apis/rpc/index.mdx

+119-59
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,24 @@ title: Remote-procedure call (RPC)
44
head: []
55
description: The built-in, JavaScript-native RPC system built into Workers and
66
Durable Objects.
7-
87
---
98

10-
import { DirectoryListing, Render, Stream, WranglerConfig } from "~/components"
9+
import {
10+
DirectoryListing,
11+
Render,
12+
Stream,
13+
WranglerConfig,
14+
TypeScriptExample,
15+
} from "~/components";
1116

1217
:::note
1318
To use RPC, [define a compatibility date](/workers/configuration/compatibility-dates) of `2024-04-03` or higher, or include `rpc` in your [compatibility flags](/workers/configuration/compatibility-flags/#nodejs-compatibility-flag).
1419
:::
1520

1621
Workers provide a built-in, JavaScript-native [RPC (Remote Procedure Call)](https://en.wikipedia.org/wiki/Remote_procedure_call) system, allowing you to:
1722

18-
* Define public methods on your Worker that can be called by other Workers on the same Cloudflare account, via [Service Bindings](/workers/runtime-apis/bindings/service-bindings/rpc)
19-
* Define public methods on [Durable Objects](/durable-objects) that can be called by other workers on the same Cloudflare account that declare a binding to it.
23+
- Define public methods on your Worker that can be called by other Workers on the same Cloudflare account, via [Service Bindings](/workers/runtime-apis/bindings/service-bindings/rpc)
24+
- Define public methods on [Durable Objects](/durable-objects) that can be called by other workers on the same Cloudflare account that declare a binding to it.
2025

2126
The RPC system is designed to feel as similar as possible to calling a JavaScript function in the same Worker. In most cases, you should be able to write code in the same way you would if everything was in a single Worker.
2227

@@ -42,11 +47,11 @@ As an exception to Structured Clone, application-defined classes (or objects wit
4247

4348
The RPC system also supports a number of types that are not Structured Cloneable, including:
4449

45-
* Functions, which are replaced by stubs that call back to the sender.
46-
* Application-defined classes that extend `RpcTarget`, which are similarly replaced by stubs.
47-
* [ReadableStream](/workers/runtime-apis/streams/readablestream/) and [WriteableStream](/workers/runtime-apis/streams/writablestream/), with automatic streaming flow control.
48-
* [Request](/workers/runtime-apis/request/) and [Response](/workers/runtime-apis/response/), for conveniently representing HTTP messages.
49-
* RPC stubs themselves, even if the stub was received from a third Worker.
50+
- Functions, which are replaced by stubs that call back to the sender.
51+
- Application-defined classes that extend `RpcTarget`, which are similarly replaced by stubs.
52+
- [ReadableStream](/workers/runtime-apis/streams/readablestream/) and [WriteableStream](/workers/runtime-apis/streams/writablestream/), with automatic streaming flow control.
53+
- [Request](/workers/runtime-apis/request/) and [Response](/workers/runtime-apis/response/), for conveniently representing HTTP messages.
54+
- RPC stubs themselves, even if the stub was received from a third Worker.
5055

5156
## Functions
5257

@@ -77,38 +82,40 @@ main = "./src/counter.js"
7782

7883
</WranglerConfig>
7984

80-
```js
85+
<TypeScriptExample>
86+
87+
```ts
8188
import { WorkerEntrypoint, RpcTarget } from "cloudflare:workers";
8289

8390
class Counter extends RpcTarget {
84-
#value = 0;
91+
#value = 0;
8592

86-
increment(amount) {
87-
this.#value += amount;
88-
return this.#value;
89-
}
93+
increment(amount: number) {
94+
this.#value += amount;
95+
return this.#value;
96+
}
9097

91-
get value() {
92-
return this.#value;
93-
}
98+
get value() {
99+
return this.#value;
100+
}
94101
}
95102

96103
export class CounterService extends WorkerEntrypoint {
97-
async newCounter() {
98-
return new Counter();
99-
}
104+
async newCounter() {
105+
return new Counter();
106+
}
100107
}
101108

102109
export default {
103-
fetch() {
104-
return new Response("ok")
105-
}
106-
}
110+
fetch() {
111+
return new Response("ok");
112+
},
113+
};
107114
```
108115

109-
The method `increment` can be called directly by the client, as can the public property `value`:
110-
116+
</TypeScriptExample>
111117

118+
The method `increment` can be called directly by the client, as can the public property `value`:
112119

113120
<WranglerConfig>
114121

@@ -122,22 +129,26 @@ services = [
122129

123130
</WranglerConfig>
124131

125-
```js
132+
<TypeScriptExample>
133+
134+
```ts
126135
export default {
127-
async fetch(request, env) {
128-
using counter = await env.COUNTER_SERVICE.newCounter();
136+
async fetch(request: Request, env: Env) {
137+
using counter = await env.COUNTER_SERVICE.newCounter();
129138

130-
await counter.increment(2); // returns 2
131-
await counter.increment(1); // returns 3
132-
await counter.increment(-5); // returns -2
139+
await counter.increment(2); // returns 2
140+
await counter.increment(1); // returns 3
141+
await counter.increment(-5); // returns -2
133142

134-
const count = await counter.value; // returns -2
143+
const count = await counter.value; // returns -2
135144

136-
return new Response(count);
137-
}
138-
}
145+
return new Response(count);
146+
},
147+
};
139148
```
140149

150+
</TypeScriptExample>
151+
141152
:::note
142153

143154
Refer to [Explicit Resource Management](/workers/runtime-apis/rpc/lifecycle) to learn more about the `using` declaration shown in the example above.
@@ -161,59 +172,75 @@ Classes which do not inherit `RpcTarget` cannot be sent over RPC at all. This di
161172

162173
When you call an RPC method and get back an object, it's common to immediately call a method on the object:
163174

164-
```js
175+
<TypeScriptExample>
176+
177+
```ts
165178
// Two round trips.
166179
using counter = await env.COUNTER_SERVICE.getCounter();
167180
await counter.increment();
168181
```
169182

183+
</TypeScriptExample>
184+
170185
But consider the case where the Worker service that you are calling may be far away across the network, as in the case of [Smart Placement](/workers/runtime-apis/bindings/service-bindings/#smart-placement) or [Durable Objects](/durable-objects). The code above makes two round trips, once when calling `getCounter()`, and again when calling `.increment()`. We'd like to avoid this.
171186

172187
With most RPC systems, the only way to avoid the problem would be to combine the two calls into a single "batch" call, perhaps called `getCounterAndIncrement()`. However, this makes the interface worse. You wouldn't design a local interface this way.
173188

174189
Workers RPC allows a different approach: You can simply omit the first `await`:
175190

176-
```js
191+
<TypeScriptExample>
192+
193+
```ts
177194
// Only one round trip! Note the missing `await`.
178195
using promiseForCounter = env.COUNTER_SERVICE.getCounter();
179196
await promiseForCounter.increment();
180197
```
181198

199+
</TypeScriptExample>
200+
182201
In this code, `getCounter()` returns a promise for a counter. Normally, the only thing you would do with a promise is `await` it. However, Workers RPC promises are special: they also allow you to initiate speculative calls on the future result of the promise. These calls are sent to the server immediately, without waiting for the initial call to complete. Thus, multiple chained calls can be completed in a single round trip.
183202

184203
How does this work? The promise returned by an RPC is not a real JavaScript `Promise`. Instead, it is a custom ["Thenable"](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise#thenables). It has a `.then()` method like `Promise`, which allows it to be used in all the places where you'd use a normal `Promise`. For instance, you can `await` it. But, in addition to that, an RPC promise also acts like a stub. Calling any method name on the promise forms a speculative call on the promise's eventual result. This is known as "promise pipelining".
185204

186205
This works when calling properties of objects returned by RPC methods as well. For example:
187206

188-
```js
207+
<TypeScriptExample>
208+
209+
```ts
189210
import { WorkerEntrypoint } from "cloudflare:workers";
190211

191212
export class MyService extends WorkerEntrypoint {
192-
async foo() {
193-
return {
194-
bar: {
195-
baz: () => "qux"
196-
}
197-
}
198-
}
213+
async foo() {
214+
return {
215+
bar: {
216+
baz: () => "qux",
217+
},
218+
};
219+
}
199220
}
200221
```
201222

202-
```js
223+
</TypeScriptExample>
224+
225+
<TypeScriptExample>
226+
227+
```ts
203228
export default {
204-
async fetch(request, env) {
205-
using foo = env.MY_SERVICE.foo();
206-
let baz = await foo.bar.baz();
207-
return new Response(baz);
208-
}
209-
}
229+
async fetch(request, env) {
230+
using foo = env.MY_SERVICE.foo();
231+
let baz = await foo.bar.baz();
232+
return new Response(baz);
233+
},
234+
};
210235
```
211236

237+
</TypeScriptExample>
238+
212239
If the initial RPC ends up throwing an exception, then any pipelined calls will also fail with the same exception
213240

214241
## ReadableStream, WriteableStream, Request and Response
215242

216-
You can send and receive [`ReadableStream`](/workers/runtime-apis/streams/readablestream/), [`WriteableStream`](/workers/runtime-apis/streams/writablestream/), [`Request`](/workers/runtime-apis/request/), and [`Response`](/workers/runtime-apis/response/) using RPC methods. When doing so, bytes in the body are automatically streamed with appropriate flow control.
243+
You can send and receive [`ReadableStream`](/workers/runtime-apis/streams/readablestream/), [`WriteableStream`](/workers/runtime-apis/streams/writablestream/), [`Request`](/workers/runtime-apis/request/), and [`Response`](/workers/runtime-apis/response/) using RPC methods. When doing so, bytes in the body are automatically streamed with appropriate flow control. This allows you to send messages over RPC which are larger than [the typical 32 MiB limit](#limitations).
217244

218245
Only [byte-oriented streams](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Using_readable_byte_streams) (streams with an underlying byte source of `type: "bytes"`) are supported.
219246

@@ -223,11 +250,15 @@ In all cases, ownership of the stream is transferred to the recipient. The sende
223250

224251
A stub received over RPC from one Worker can be forwarded over RPC to another Worker.
225252

226-
```js
253+
<TypeScriptExample>
254+
255+
```ts
227256
using counter = env.COUNTER_SERVICE.getCounter();
228257
await env.ANOTHER_SERVICE.useCounter(counter);
229258
```
230259

260+
</TypeScriptExample>
261+
231262
Here, three different workers are involved:
232263

233264
1. The calling Worker (we'll call this the "introducer")
@@ -244,13 +275,42 @@ Currently, this proxying only lasts until the end of the Workers' execution cont
244275

245276
In this video, we explore how Cloudflare Workers support Remote Procedure Calls (RPC) to simplify communication between Workers. Learn how to implement RPC in your JavaScript applications and build serverless solutions with ease. Whether you're managing microservices or optimizing web architecture, this tutorial will show you how to quickly set up and use Cloudflare Workers for RPC calls. By the end of this video, you'll understand how to call functions between Workers, pass functions as arguments, and implement user authentication with Cloudflare Workers.
246277

247-
<Stream id="d506935b6767fd07626adbec46d41e6d" title="Introduction to Workers RPC" thumbnail="2.5s" />
278+
<Stream
279+
id="d506935b6767fd07626adbec46d41e6d"
280+
title="Introduction to Workers RPC"
281+
thumbnail="2.5s"
282+
/>
248283

249284
## More Details
250285

251286
<DirectoryListing />
252287

253288
## Limitations
254289

255-
* [Smart Placement](/workers/configuration/smart-placement/) is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a [Service Binding](/workers/runtime-apis/bindings) to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine.
256-
* The maximum serialized RPC limit is 1 MB. Consider using [`ReadableStream`](/workers/runtime-apis/streams/readablestream/) when returning more data.
290+
- [Smart Placement](/workers/configuration/smart-placement/) is currently ignored when making RPC calls. If Smart Placement is enabled for Worker A, and Worker B declares a [Service Binding](/workers/runtime-apis/bindings) to it, when Worker B calls Worker A via RPC, Worker A will run locally, on the same machine.
291+
292+
- The maximum serialized RPC limit is 32 MiB. Consider using [`ReadableStream`](/workers/runtime-apis/streams/readablestream/) when returning more data.
293+
294+
<TypeScriptExample>
295+
296+
```ts
297+
export class MyService extends WorkerEntrypoint {
298+
async foo() {
299+
// Although this works, it puts a lot of memory pressure on the isolate.
300+
// If possible, streaming the data from its original source is much preferred and would yield better performance.
301+
// If you must buffer the data into memory, consider chunking it into smaller pieces if possible.
302+
303+
const sizeInBytes = 33 * 1024 * 1024; // 33 MiB
304+
const arr = new Uint8Array(sizeInBytes);
305+
306+
return new ReadableStream({
307+
start(controller) {
308+
controller.enqueue(arr);
309+
controller.close();
310+
},
311+
});
312+
}
313+
}
314+
```
315+
316+
</TypeScriptExample>
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
---
22
{}
3-
43
---
54

6-
import { WranglerConfig } from "~/components";
5+
import { WranglerConfig, TypeScriptExample } from "~/components";
76

87
For example, if Worker B implements the public method `add(a, b)`:
98

@@ -16,19 +15,25 @@ main = "./src/workerB.js"
1615

1716
</WranglerConfig>
1817

19-
```js
18+
<TypeScriptExample>
19+
20+
```ts
2021
import { WorkerEntrypoint } from "cloudflare:workers";
2122

2223
export default class extends WorkerEntrypoint {
23-
async fetch() { return new Response("Hello from Worker B"); }
24+
async fetch() {
25+
return new Response("Hello from Worker B");
26+
}
2427

25-
add(a, b) { return a + b; }
28+
add(a: number, b: number) {
29+
return a + b;
30+
}
2631
}
2732
```
2833

29-
Worker A can declare a [binding](/workers/runtime-apis/bindings) to Worker B:
30-
34+
</TypeScriptExample>
3135

36+
Worker A can declare a [binding](/workers/runtime-apis/bindings) to Worker B:
3237

3338
<WranglerConfig>
3439

@@ -44,11 +49,15 @@ services = [
4449

4550
Making it possible for Worker A to call the `add()` method from Worker B:
4651

47-
```js
52+
<TypeScriptExample>
53+
54+
```ts
4855
export default {
49-
async fetch(request, env) {
50-
const result = await env.WORKER_B.add(1, 2);
51-
return new Response(result);
52-
}
53-
}
56+
async fetch(request, env) {
57+
const result = await env.WORKER_B.add(1, 2);
58+
return new Response(result);
59+
},
60+
};
5461
```
62+
63+
</TypeScriptExample>

0 commit comments

Comments
 (0)