In the traditional client-server model of distributed systems, what travels across the network is data. A client sends a request; the server processes it and returns a result. The code that performs the computation stays put. For many applications, this paradigm is sufficient and elegant.
But sometimes passing data is not enough. Consider a search engine crawling the web: shipping every page to a central indexer is hopelessly inefficient compared to sending a crawler program to each host and letting it extract and return only the relevant information. Consider a mobile agent that visits multiple marketplaces, negotiates on your behalf, and returns with the best deal — the agent carries both its behaviour and its evolving state as it roams.
Code mobility is the capability to dynamically change the bindings between the components of a distributed system — relocating computations, data, or both — at runtime. When the cost of moving data exceeds the cost of moving code, or when you want to keep data and the code that processes it together, code mobility becomes the right abstraction.
Two fundamental motivations drive code mobility:
The foundational reference for code mobility is Fuggetta, Picco, and Vigna, "Understanding Code Mobility," IEEE TSE 24(5), 1998 — the paper that first gave the field a unified conceptual framework. Much of the terminology in this lesson traces back to that work.
Process migration — moving a running computation along with its context from one machine to another — is the traditional form of code mobility. The literature on process migration (see Milojicic et al., 2000) identifies several motivations, all of which remain relevant today:
| Reason | What it means |
|---|---|
| Load balancing | Redistribute computation across nodes to avoid hotspots and utilise idle resources. |
| Minimising communication | Move computation close to the data it processes, reducing the volume and latency of network traffic. |
| Optimising perceived performance | Place code where it can respond faster to users (e.g., edge computing, CDN edge functions). |
| Improving scalability | Dynamically spawn or relocate components as the system grows, without central bottlenecks. |
| Dynamic configurability | Inject new behaviour into a running system without redeploying the whole application. |
| Fault tolerance | Migrate processes away from failing or soon-to-be-maintained machines. |
"Give three concrete scenarios where code mobility is preferable to data mobility, and justify each." Be ready to contrast code mobility with the alternatives — remote procedure calls, message passing, or shipping data — for each scenario.
To understand what it means to "move code", we need a precise model of what a running process actually consists of. Fuggetta et al. decompose a process into three conceptual segments:
| Segment | Contains | Examples |
|---|---|---|
| Code segment | The set of executable instructions composing the process — the program text. | Compiled bytecode, JavaScript source, a WASM binary. |
| Execution segment | The private data, the stack, and the program counter — the current execution state. | Local variables, call stack frames, the next instruction pointer. |
| Resource segment | References to external resources the process depends on to run. | File handles, socket connections, printers, database connections, other processes. |
The kind of code mobility you achieve depends on which of these three segments are transferred from the source machine to the target. The code segment alone gives weak mobility; the code plus execution segment gives strong mobility; the resource segment brings the hardest challenges.
This decomposition is the conceptual backbone of the entire code mobility taxonomy. Every model we will study is defined by which segments travel, who initiates the transfer, and how the migrated code relates to the target environment.
graph TD
P[Process] --> CS[Code Segment
Executable instructions]
P --> ES[Execution Segment
Private data, stack, PC]
P --> RS[Resource Segment
External references]
CS --> |Transfer| WM[Weak Mobility]
CS --> |Transfer| SM[Strong Mobility]
ES --> |Transfer| SM
RS --> |Bindings matter| MIG[Determines migration
actions at target]
Weak mobility is the simplest form of code migration: only the code segment is transferred, possibly accompanied by some initialisation parameters. The execution state is not carried along — the code starts afresh at the destination, as if invoked for the first time.
The main idea is straightforward: the code can be executed ex novo every time it arrives. There is no need to preserve a pre-existing computational context because either the context is irrelevant or the target machine's context is exactly what we want.
| Aspect | Description |
|---|---|
| What moves | Only the code (plus optional initialisation data). |
| What stays | Execution state, resource bindings. |
| Requirement | The target machine must be able to execute the code (compatible runtime). |
| Complexity | Low — no need to capture, serialise, or restore execution state. |
| Canonical examples | Java Applets, JavaScript fetched by a browser, remote evaluation in RPC-based systems. |
Weak mobility is easy to implement but limited in expressiveness. You cannot suspend a computation on machine A and resume it on machine B — the code always restarts. For iterative, stateful computations that need to roam (like a mobile agent bargaining across multiple marketplaces), weak mobility is insufficient.
Strong mobility transfers both the code segment and the execution segment. A process can be stopped at an arbitrary point, its entire execution state captured (stack, program counter, private data), shipped to another machine, and then resumed exactly where it left off.
The benefit is transformational: a single logical thread of control can physically traverse multiple machines, accumulating state and making decisions along the way, without ever losing its place. This is the foundation of the mobile agent paradigm.
| Aspect | Description |
|---|---|
| What moves | Code segment + execution segment (stack, PC, private data). |
| What stays | Resource segment (bindings must be re-established or adapted). |
| Requirement | The middleware or runtime must support capturing and restoring execution state — this is demanding. |
| Complexity | High — requires language-level or VM-level support (e.g., continuations, checkpoint/restart). |
| Canonical examples | Mobile agents (e.g., Aglets, Agent Tcl), process migration in MOSIX, VM live migration. |
Strong mobility is rare in mainstream platforms precisely because of the implementation burden. Languages like Java would need VM support to capture the full stack and heap state. In practice, many systems that claim "agent" capabilities actually implement weak mobility — the agent carries a program counter variable and uses a dispatch loop to simulate resumption, rather than truly capturing the native stack.
"Why is strong mobility difficult to implement in practice? What runtime support does it require?" Be prepared to discuss stack capture, serialisation of closures, heterogeneous architectures, and the security implications of accepting foreign execution state.
Based on who initiates the migration (sender or receiver) and what is transferred (code only, or code plus execution state), we can classify distributed systems into four canonical mobility models. This taxonomy comes from Tanenbaum & van Steen (2017) and is essential for reasoning about any system that involves code movement.
| Model | Initiator | Mobility | What travels | Examples |
|---|---|---|---|---|
| Client-Server (CS) | N/A | None | Data only (requests and replies) | HTTP, SQL queries, traditional RPC |
| Code-on-Demand (COD) | Receiver | Weak | Code (know-how) | Java Applets, JavaScript in browsers |
| Remote Evaluation (REV) | Sender | Weak | Code (know-how) | Stored procedures, serverless functions, eval servers |
| Mobile Agents (MA) | Sender | Strong | Code + execution state | Aglets, Agent Tcl, Web crawlers with state |
The key difference between client-server and the other three is that in CS, the code that executes at the server is already there — only data flows. In COD, REV, and MA, the know-how (the program itself) is transferred across the network.
The difference between COD and REV is direction: COD is pulled by the receiver ("give me the code to execute"), while REV is pushed by the sender ("here, execute this code for me"). MA adds the execution segment on top of REV's push model.
Explore the four canonical mobility models. Each state represents a paradigm; transitions show how adding mobility (weak or strong) and changing the initiator (sender vs receiver) moves you from one model to the next.
The initiator of code migration has profound implications for the interaction model, the security requirements, and the complexity of the overall system.
Migration is triggered by the machine where the code currently resides or executes. The sender pushes code (and possibly execution state) to a target machine.
Migration is triggered by the target machine, which requests new behaviour from a code source (typically a server). The receiver pulls code toward itself.
"Compare sender-initiated and receiver-initiated migration in terms of security, anonymity, and the complexity of the interaction scheme. Give a concrete example for each."
When weak mobility delivers code to a machine, a design decision must be made: should the incoming code execute in the same process as the receiver, or in a separate, isolated process?
| Strategy | How it works | Pros | Cons |
|---|---|---|---|
| Target process execution | The mobile code runs directly in the address space of the receiving process (e.g., a browser's rendering engine running JavaScript). | No inter-process communication overhead; direct access to the host's data structures; simpler integration. | Malicious or buggy code can corrupt the host process; a crash in the mobile code can bring down the host. |
| Separate process execution | The mobile code is assigned to a dedicated process with its own address space; communication with the host happens via IPC. | Strong isolation — a crash or exploit in the mobile code cannot directly compromise the host; resource usage can be capped. | IPC overhead; more complex to set up and manage; sharing state requires explicit marshalling. |
The Java Applet model originally ran in the browser's process (target process), which led to numerous security vulnerabilities. Modern browsers isolate each tab in a separate process and use sandboxing techniques, implementing a hybrid approach: separate-process execution with controlled, mediated access to host resources.
The choice between target-process and separate-process execution is fundamentally a trade-off between performance and protection. It is the same tension that appears in microkernel vs monolithic kernel design, and in container vs VM isolation.
Strong mobility can also be supported through remote cloning: instead of moving a process, you create an exact copy of it on a remote machine. The original process continues to run on the source machine while the clone executes in parallel on the target.
Cloning differs from migration in several important ways:
| Aspect | Migration | Cloning |
|---|---|---|
| Original process | Stops on source; resumes on target. | Continues running on source. |
| Number of instances | Exactly one at any time. | Two (or more) running in parallel. |
| Distribution transparency | Partial — process moves, but clients must know where it is. | Improved — replicated instances can serve requests from multiple locations. |
| Classic example | MOSIX process migration. | UNIX fork() followed by remote execution of the child. |
Cloning improves distribution transparency because the replicated processes can appear as a single logical service to clients, with requests routed to the nearest or least-loaded replica. This idea underlies modern patterns like function replication in edge computing and actor-model systems (e.g., Akka cluster sharding).
So far we have discussed moving the code segment and the execution segment. But a process does not run in a vacuum — it depends on external resources: files, databases, printers, network sockets, other processes. These are referenced through the resource segment.
Resources pose two fundamental challenges during migration:
The resource segment is the hardest part of code mobility. Code and execution state are digital and relatively small; resources are often large, physically constrained, or externally controlled. The taxonomy of resource bindings (next two sections) provides a framework for reasoning about what actions are needed when code moves.
The first dimension of the resource problem is: how does the migrating process refer to its resources? There are three binding types:
| Binding type | Meaning | Example | Migration implication |
|---|---|---|---|
| By identifier | The process needs a resource with a specific name or address. | A URL (https://api.example.com/v1), a DNS name, a local file path, a process ID. | If the identifier is a global name (URL), it may still work at the target. If it is local (PID, local path), it will break and must be rebound. |
| By value | The process needs a resource based on its content, not its identity. | A specific code library (e.g., "I need NumPy v1.24"), a configuration file with known content. | The resource can potentially be copied or substituted with an equivalent one at the target, as long as the value matches. |
| By type | The process needs a resource of a given category, any instance will do. | A printer, a display, a GPU, a network interface. | The target machine must provide a resource of the same type. The binding can be re-established locally if such a resource exists. |
"Given a process that opens a local SQLite database file, downloads a Python library, and renders output to a screen, classify each resource dependency by binding type and explain the migration implications."
The second dimension is: how tightly is the resource bound to the machine that currently hosts it? This determines whether the resource can be moved at all, and at what cost:
| Binding type | Meaning | Examples | Can it move? |
|---|---|---|---|
| Unattached | Resources that can be easily moved between machines with minimal cost. | Files associated with the migrating code (a configuration file, a log file), cached data. | Yes — they can be transferred along with the code, or fetched on demand. |
| Fastened | Resources that could theoretically be moved, but at a significant cost (size, bandwidth, downtime). | A local database, a large dataset, a Docker image. | Possible but expensive — often better to leave it in place and access it remotely, or replicate it lazily. |
| Fixed | Resources intrinsically bound to a specific machine or physical location. | A monitor, a printer, a specialised hardware device, a GPS receiver. | No — the resource cannot move. The process must either give it up or rebind to an equivalent at the target. |
The 3x3 matrix formed by process-to-resource binding (identifier/value/type) and resource-to-machine binding (unattached/fastened/fixed) defines, for each combination, the set of actions the migration system must take. This is the subject of the next two sections.
The following pseudocode illustrates the four canonical mobility patterns. Click each line to reveal its explanation.
When code migrates to a new machine, the references in its resource segment must be adapted. Tanenbaum & van Steen (2017) identify the actions to be taken for each combination of process-to-resource binding and resource-to-machine binding. The following table summarises the decision matrix:
| Resource-to-machine → Process-to-resource ↓ | Unattached | Fastened | Fixed |
|---|---|---|---|
| By identifier | Move the resource (MV) or rebind to a global reference (GR). | Rebind to a global reference (GR) — the resource stays but must be reachable via a network identifier. | Rebind to a local resource of the same type at the target (GR) — or give up the resource. |
| By value | Copy the resource value (CP) to the target. | Copy the resource (CP) if feasible; otherwise establish a remote reference. | Copy is meaningless for fixed resources; rebind to an equivalent at the target (GR). |
| By type | Rebind to a resource of the same type at the target (GR). | Rebind to a local resource of the same type (GR) or access the fastened one remotely. | Rebind to a local resource of the same type at the target (GR). |
The possible actions referenced above:
Use the explorer below to understand which migration action is appropriate for each combination of binding types. Click any cell in the matrix to see a concrete scenario.
Code mobility is needed when passing data is not enough — for load balancing, minimising communication, improving scalability, enabling dynamic reconfiguration, and increasing fault tolerance.
A process decomposes into code, execution, and resource segments. Which segments travel determines the class of mobility: weak (code only) or strong (code + execution).
Only the code segment moves. The program restarts from the beginning at the destination. Simple to implement, sufficient for code-on-demand and remote evaluation.
Both code and execution state travel. The process suspends, moves, and resumes exactly where it left off. Demanding to implement, but enables mobile agents.
Client-Server (no mobility), Code-on-Demand (receiver-initiated, weak), Remote Evaluation (sender-initiated, weak), Mobile Agents (sender-initiated, strong).
Sender-initiated migration is more complex (targets need heavy security) but enables proactive distribution. Receiver-initiated is simpler (anonymous clients, lightweight security) but the server retains control.
The resource segment is the hardest challenge — bindings can be by identifier, value, or type; resources can be unattached, fastened, or fixed. The 3x3 matrix determines whether to move, copy, or rebind.
Weak mobility transfers only the code segment (plus optional initialisation data). The code starts execution from the beginning at the destination. Strong mobility transfers both the code segment and the execution segment (private data, stack, program counter), allowing the process to resume exactly where it was suspended. The key distinction is whether the execution state is captured and restored.
(a) Code-on-Demand (COD) — receiver (browser) pulls code. (b) Remote Evaluation (REV) — sender (client) pushes code to be executed remotely. (c) Mobile Agents (MA) — the crawler is a sender-initiated agent carrying both code and execution state (the visited-URL index) across sites. (d) Client-Server (CS) — no code mobility, only data (the request and the response) flows.
In receiver-initiated migration, the target initiates the transfer — it asks for code and knows what it is getting. Only a few client-side resources need protection (sandboxing the incoming code). Clients can remain anonymous — the server does not need to track who downloaded code. In sender-initiated migration, the target receives code it did not ask for, and must authenticate the sender, verify the code's integrity, and protect its resources from potentially malicious behaviour. The server must know about clients, making the interaction more complex.
SQLite file: (i) Binding by identifier (local file path). (ii) Unattached or fastened depending on size. (iii) If unattached, MV (move the file). If fastened, GR (rebind to a network-accessible copy or copy lazily). Python library: (i) Binding by value (the library's content, identified by name and version). (ii) Unattached. (iii) CP (copy/install the library at the target). Printer: (i) Binding by type (any printer will do, or by identifier if a specific named printer). (ii) Fixed. (iii) GR (rebind to a printer available at the target machine).
With migration, a single process instance moves from one machine to another; clients must track its current location to reach it. With cloning, multiple replicas of the same process exist simultaneously on different machines. Clients can be routed to the nearest replica, and the system can present a single logical service endpoint. This transparent replication hides the physical distribution from clients — they do not need to know which replica serves their request, and failover to another replica is seamless if one fails.
Weak mobility sufficient: A serverless function (e.g., AWS Lambda) — the code is stateless and each invocation is independent; the platform just needs the function code and input parameters. There is no execution state to carry. Strong mobility necessary: A negotiation agent that visits multiple e-commerce sites, gathers quotes, and makes a decision based on accumulated information. The agent must remember which sites it visited and what offers it received; suspending and resuming its decision logic across sites requires carrying the execution state.
MV (Move): Transfer the resource itself. Example: a small configuration file specific to the migrating process is sent along with the code to the target. CP (Copy): Create a copy of the resource at the target; the original stays. Example: copying a read-only dataset that the process needs at both locations. GR (Global Rebind): Update the reference to point to a globally reachable resource or a local equivalent. Example: a process that used a local database on the source machine rebinds to an equivalent database instance accessible at the target site.