{"record":{"id":"aa2a2df405e40fd9","repo":"jax-ml/jax","slug":"cannot-wait-send-on-a-local-copy","errorCode":null,"errorMessage":"Cannot `wait_send` on a local copy.","messagePattern":"Cannot `wait_send` on a local copy\\.","errorType":"exception","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"jax/_src/pallas/mosaic/primitives.py","lineNumber":242,"sourceCode":"    )\n\n  def wait(self):\n    if self.is_remote:\n      self.wait_send()\n    self.wait_recv()\n\n  def wait_recv(self):\n    self._used = True\n    flat_args, tree = self._get_args_and_tree()\n    dma_wait_p.bind(\n        *flat_args, tree=tree, device_id_type=self.device_id_type,\n        insert_dummy_device=False, is_wait_send=False\n    )\n\n  def wait_send(self):\n    self._used = True\n    if not self.is_remote:\n      raise ValueError(\"Cannot `wait_send` on a local copy.\")\n    # We swap src and dst since by default dma_wait_p waits on the dst_sem\n    # TODO(rdyro): Update the lowering to use `is_wait_send` instead of\n    # swapping src and dst.\n    flat_args, tree = self._get_args_and_tree(\n        swap_src_and_dst=True,\n    )\n    dma_wait_p.bind(\n        *flat_args, tree=tree, device_id_type=self.device_id_type,\n        insert_dummy_device=self.is_remote,\n        is_wait_send=True,\n    )\n\n\ndef _dma_flatten(*args):\n  flat_tree = ft.flatten(args)\n  return flat_tree.vals, flat_tree.tree\n\n","sourceCodeStart":224,"sourceCodeEnd":260,"githubUrl":"https://github.com/jax-ml/jax/blob/1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb/jax/_src/pallas/mosaic/primitives.py#L224-L260","documentation":"wait_send blocks until a REMOTE DMA's source-side semaphore is signaled (it waits on the send completing on the other device). On a purely local copy there is no send side to wait on, so calling wait_send on a non-remote DMA is a user error and raises ValueError.","triggerScenarios":"Calling descriptor.wait_send() on a DMA started without a device_id (i.e., is_remote is False). Common when generic pipelining code (scalar_subcore_fn body) unconditionally waits on sends for all DMAs including local ones.","commonSituations":"Writing a pipeline that issues many DMAs and calls wait_send on each; enabling send-wait only needed for cross-die copies but applying it uniformly; forgetting that local copies only support wait_receive-style waiting.","solutions":["Branch on is_remote: only call wait_send when dma.is_remote is true","Use the local equivalent (e.g. wait on the destination semaphore) for local copies","Gate send-waiting behind whether src_sem/device_id were provided at DMA construction"],"exampleFix":"# before\nfor dma in dmas:\n  dma.wait_send()\n\n# after\nfor dma in dmas:\n  if dma.is_remote:\n    dma.wait_send()","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"def safe_wait_send(dma):\n  if getattr(dma, 'is_remote', False):\n    dma.wait_send()\n  # local copies: no send side to wait on","tryCatchPattern":"try:\n  dma.wait_send()\nexcept ValueError:\n  pass  # local copy; nothing to wait on","preventionTips":["Always branch on is_remote before wait_send","Centralize DMA wait logic in one helper that knows about locality"],"tags":["jax","pallas","mosaic","dma","remote-copy","wait-send"],"backgroundTag":"invalid-operation-for-local-copy","analyzedSha":"1e1c6a8fc06dfcd1247076ec5cae4640cea5d7bb","analyzedAt":"2026-08-27T09:53:25.647Z","schemaVersion":2},"datasetVersion":"2026-08-27T13:17:12.746Z"}