Repository: vmware/govmomi
Pull request #2351 (2021-04-06 03:31:11)

simulator.Context locking, Async Tasks, and Task Delay
This PR is broken into 4 commits:

- Tie simulator locking to the Context
- Make simulator tasks asynchronous
- Add support for task delay
- Ensure "hand off" from handler func to async task goroutine

It may be easiest to review each commit individually!

# Tie simulator locking to the Context

When the simulator dispatches a call, it locks the handler object, calls the handler function, then unlocks.
This is fine when the handler function doesn't need to lock anything else, and so long as the handler function does all of its work before completing. However, there are paths (like VM creation) where the handler function *does* lock other objects, and asynchronous tasks are closer to how a real vCenter would behave.

This commit replaces the prior lock elision policy of "if I'm locking the handler object, skip the lock" (see `Context.WithLock()`) with a scheme where all locks are associated with the request's `simulator.Context`. If a path tries to acquire a lock that the Context already holds, a reference to the lock is tracked, but the `.Lock()` is elided. This effectively means that locks are "shared" for a particular Context.

# Make simulator tasks asynchronous

Async tasks are closer to real vCenter behavior, and they are necessary to enable task-level delay (ie, simulating a long-running task).  This change essentially runs the existing `task.Run()` code in a goroutine. It also requires Context arguments for any handler functions that spawn a task, because all tasks must know which Context they're running in (to enable locking; see previous commit).

There were many places where synchronous tasks were implicitly assumed. So these have been fixed.

The simulator unit tests were run tens of thousands of times to shake out race conditions. Some of those have been fixed in other PRs. There remain racy tests that were racy before this PR, including

- TestRaceDestroy is just an inherently racy test, and fails sometimes.
- TestRace deadlocks every ~20k runs. My hunch is that this is a side effect of the inter-request (now inter-Context) lock ordering issues inherent to the simulator's overall approach to locking.
- TestSessionManagerLoginExtension
- ExampleHandlerREST: I have once observed a keepalive `handler.Stop()` and `handler.Start()` deadlock. This seems to be possible if the `Start()` goroutine fires while `Stop()` is waiting for it to finish:
  - `Stop()` holds the handler mutex while it waits on the waitGroup
  - `Start()` goroutine needs to acquire the handler mutex to re-enter `Start()` (`send()` ends up here)
  - The fact that the `Start()` goroutine ends up calling `Start()` feels like it may be part of the problem, but I haven't investigated fully.
- TestHandlerREST: Discussed in PR comments. Scheduling/timing anomaly, perhaps?

# Add support for task delay

Adds 2 tests: one to test the prior "API level" delay (which simulates a slow-to-respond vCenter), and one to test the new "task level" delay (which simulates a task taking a long time).

A `TaskDelay` var allows customization of the task delay; it can be configured globally or per-method, much like API level delays.

# Ensure "hand off" from handler func to async task goroutine

This commit guarantees that a lock held by the handler func (ie, the lock on the handler itself) is held by the async goroutine without the possibility of someone else getting the lock in between.

Without this behavior, there could be confusing Time-of-check vs. Time-of-use bugs, where the handler func checks some lock-protected data, spawns a task based on that data, but by the time the task runs, that data has changed.


Patch:
diff --git a/cns/simulator/simulator.go b/cns/simulator/simulator.go
index 1b089b73..2598139e 100644
--- a/cns/simulator/simulator.go
+++ b/cns/simulator/simulator.go
@@ -62,7 +62,7 @@ type CnsVolumeManager struct {
 
 const simulatorDiskUUID = "6000c298595bf4575739e9105b2c0c2d"
 
-func (m *CnsVolumeManager) CnsCreateVolume(ctx context.Context, req *cnstypes.CnsCreateVolume) soap.HasFault {
+func (m *CnsVolumeManager) CnsCreateVolume(ctx *simulator.Context, req *cnstypes.CnsCreateVolume) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsCreateVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		if len(req.CreateSpecs) == 0 {
 			return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsVolumeCreateSpec"}
@@ -160,7 +160,7 @@ func (m *CnsVolumeManager) CnsCreateVolume(ctx context.Context, req *cnstypes.Cn
 
 	return &methods.CnsCreateVolumeBody{
 		Res: &cnstypes.CnsCreateVolumeResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -237,7 +237,7 @@ func (m *CnsVolumeManager) CnsQueryAllVolume(ctx context.Context, req *cnstypes.
 	}
 }
 
-func (m *CnsVolumeManager) CnsDeleteVolume(ctx context.Context, req *cnstypes.CnsDeleteVolume) soap.HasFault {
+func (m *CnsVolumeManager) CnsDeleteVolume(ctx *simulator.Context, req *cnstypes.CnsDeleteVolume) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsDeleteVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		operationResult := []cnstypes.BaseCnsVolumeOperationResult{}
 		for _, volumeId := range req.VolumeIds {
@@ -259,13 +259,13 @@ func (m *CnsVolumeManager) CnsDeleteVolume(ctx context.Context, req *cnstypes.Cn
 
 	return &methods.CnsDeleteVolumeBody{
 		Res: &cnstypes.CnsDeleteVolumeResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
 // CnsUpdateVolumeMetadata simulates UpdateVolumeMetadata call for simulated vc
-func (m *CnsVolumeManager) CnsUpdateVolumeMetadata(ctx context.Context, req *cnstypes.CnsUpdateVolumeMetadata) soap.HasFault {
+func (m *CnsVolumeManager) CnsUpdateVolumeMetadata(ctx *simulator.Context, req *cnstypes.CnsUpdateVolumeMetadata) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsUpdateVolumeMetadata", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		if len(req.UpdateSpecs) == 0 {
 			return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsUpdateVolumeMetadataSpec"}
@@ -291,13 +291,13 @@ func (m *CnsVolumeManager) CnsUpdateVolumeMetadata(ctx context.Context, req *cns
 	})
 	return &methods.CnsUpdateVolumeBody{
 		Res: &cnstypes.CnsUpdateVolumeMetadataResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
 // CnsAttachVolume simulates AttachVolume call for simulated vc
-func (m *CnsVolumeManager) CnsAttachVolume(ctx context.Context, req *cnstypes.CnsAttachVolume) soap.HasFault {
+func (m *CnsVolumeManager) CnsAttachVolume(ctx *simulator.Context, req *cnstypes.CnsAttachVolume) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsAttachVolume", func(task *simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		if len(req.AttachSpecs) == 0 {
 			return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsAttachVolumeSpec"}
@@ -327,13 +327,13 @@ func (m *CnsVolumeManager) CnsAttachVolume(ctx context.Context, req *cnstypes.Cn
 
 	return &methods.CnsAttachVolumeBody{
 		Res: &cnstypes.CnsAttachVolumeResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
 // CnsDetachVolume simulates DetachVolume call for simulated vc
-func (m *CnsVolumeManager) CnsDetachVolume(ctx context.Context, req *cnstypes.CnsDetachVolume) soap.HasFault {
+func (m *CnsVolumeManager) CnsDetachVolume(ctx *simulator.Context, req *cnstypes.CnsDetachVolume) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsDetachVolume", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		if len(req.DetachSpecs) == 0 {
 			return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsDetachVolumeSpec"}
@@ -358,13 +358,13 @@ func (m *CnsVolumeManager) CnsDetachVolume(ctx context.Context, req *cnstypes.Cn
 	})
 	return &methods.CnsDetachVolumeBody{
 		Res: &cnstypes.CnsDetachVolumeResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
 // CnsExtendVolume simulates ExtendVolume call for simulated vc
-func (m *CnsVolumeManager) CnsExtendVolume(ctx context.Context, req *cnstypes.CnsExtendVolume) soap.HasFault {
+func (m *CnsVolumeManager) CnsExtendVolume(ctx *simulator.Context, req *cnstypes.CnsExtendVolume) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsExtendVolume", func(task *simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		if len(req.ExtendSpecs) == 0 {
 			return nil, &vim25types.InvalidArgument{InvalidProperty: "CnsExtendVolumeSpec"}
@@ -394,12 +394,12 @@ func (m *CnsVolumeManager) CnsExtendVolume(ctx context.Context, req *cnstypes.Cn
 
 	return &methods.CnsExtendVolumeBody{
 		Res: &cnstypes.CnsExtendVolumeResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *CnsVolumeManager) CnsQueryVolumeInfo(ctx context.Context, req *cnstypes.CnsQueryVolumeInfo) soap.HasFault {
+func (m *CnsVolumeManager) CnsQueryVolumeInfo(ctx *simulator.Context, req *cnstypes.CnsQueryVolumeInfo) soap.HasFault {
 	task := simulator.CreateTask(m, "CnsQueryVolumeInfo", func(*simulator.Task) (vim25types.AnyType, vim25types.BaseMethodFault) {
 		operationResult := []cnstypes.BaseCnsVolumeOperationResult{}
 		for _, volumeId := range req.VolumeIds {
@@ -452,7 +452,7 @@ func (m *CnsVolumeManager) CnsQueryVolumeInfo(ctx context.Context, req *cnstypes
 
 	return &methods.CnsQueryVolumeInfoBody{
 		Res: &cnstypes.CnsQueryVolumeInfoResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/cluster_compute_resource.go b/simulator/cluster_compute_resource.go
index 6930c9c0..e3d44325 100644
--- a/simulator/cluster_compute_resource.go
+++ b/simulator/cluster_compute_resource.go
@@ -36,8 +36,8 @@ type ClusterComputeResource struct {
 	ruleKey int32
 }
 
-func (c *ClusterComputeResource) RenameTask(req *types.Rename_Task) soap.HasFault {
-	return RenameTask(c, req)
+func (c *ClusterComputeResource) RenameTask(ctx *Context, req *types.Rename_Task) soap.HasFault {
+	return RenameTask(ctx, c, req)
 }
 
 type addHost struct {
@@ -67,10 +67,10 @@ func (add *addHost) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 	return host.Reference(), nil
 }
 
-func (c *ClusterComputeResource) AddHostTask(add *types.AddHost_Task) soap.HasFault {
+func (c *ClusterComputeResource) AddHostTask(ctx *Context, add *types.AddHost_Task) soap.HasFault {
 	return &methods.AddHost_TaskBody{
 		Res: &types.AddHost_TaskResponse{
-			Returnval: NewTask(&addHost{c, add}).Run(),
+			Returnval: NewTask(&addHost{c, add}).Run(ctx),
 		},
 	}
 }
@@ -309,7 +309,7 @@ func (c *ClusterComputeResource) updateOverridesVmOrchestration(cfg *types.Clust
 	return nil
 }
 
-func (c *ClusterComputeResource) ReconfigureComputeResourceTask(req *types.ReconfigureComputeResource_Task) soap.HasFault {
+func (c *ClusterComputeResource) ReconfigureComputeResourceTask(ctx *Context, req *types.ReconfigureComputeResource_Task) soap.HasFault {
 	task := CreateTask(c, "reconfigureCluster", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		spec, ok := req.Spec.(*types.ClusterConfigSpecEx)
 		if !ok {
@@ -335,7 +335,7 @@ func (c *ClusterComputeResource) ReconfigureComputeResourceTask(req *types.Recon
 
 	return &methods.ReconfigureComputeResource_TaskBody{
 		Res: &types.ReconfigureComputeResource_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/custom_fields_manager.go b/simulator/custom_fields_manager.go
index aa68119f..c26c5fc9 100644
--- a/simulator/custom_fields_manager.go
+++ b/simulator/custom_fields_manager.go
@@ -31,11 +31,11 @@ type CustomFieldsManager struct {
 
 // Iterates through all entities of passed field type;
 // Removes found field from their custom field properties.
-func entitiesFieldRemove(field types.CustomFieldDef) {
+func entitiesFieldRemove(ctx *Context, field types.CustomFieldDef) {
 	entities := Map.All(field.ManagedObjectType)
 	for _, e := range entities {
 		entity := e.Entity()
-		Map.WithLock(entity, func() {
+		ctx.WithLock(entity, func() {
 			aFields := entity.AvailableField
 			for i, aField := range aFields {
 				if aField.Key == field.Key {
@@ -65,11 +65,11 @@ func entitiesFieldRemove(field types.CustomFieldDef) {
 
 // Iterates through all entities of passed field type;
 // Renames found field in entity's AvailableField property.
-func entitiesFieldRename(field types.CustomFieldDef) {
+func entitiesFieldRename(ctx *Context, field types.CustomFieldDef) {
 	entities := Map.All(field.ManagedObjectType)
 	for _, e := range entities {
 		entity := e.Entity()
-		Map.WithLock(entity, func() {
+		ctx.WithLock(entity, func() {
 			aFields := entity.AvailableField
 			for i, aField := range aFields {
 				if aField.Key == field.Key {
@@ -102,7 +102,7 @@ func (c *CustomFieldsManager) findByKey(key int32) (int, *types.CustomFieldDef)
 	return -1, nil
 }
 
-func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) soap.HasFault {
+func (c *CustomFieldsManager) AddCustomFieldDef(ctx *Context, req *types.AddCustomFieldDef) soap.HasFault {
 	body := &methods.AddCustomFieldDefBody{}
 
 	_, field := c.findByNameType(req.Name, req.MoType)
@@ -126,7 +126,7 @@ func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) so
 	entities := Map.All(req.MoType)
 	for _, e := range entities {
 		entity := e.Entity()
-		Map.WithLock(entity, func() {
+		ctx.WithLock(entity, func() {
 			entity.AvailableField = append(entity.AvailableField, def)
 		})
 	}
@@ -140,7 +140,7 @@ func (c *CustomFieldsManager) AddCustomFieldDef(req *types.AddCustomFieldDef) so
 	return body
 }
 
-func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldDef) soap.HasFault {
+func (c *CustomFieldsManager) RemoveCustomFieldDef(ctx *Context, req *types.RemoveCustomFieldDef) soap.HasFault {
 	body := &methods.RemoveCustomFieldDefBody{}
 
 	i, field := c.findByKey(req.Key)
@@ -149,7 +149,7 @@ func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldD
 		return body
 	}
 
-	entitiesFieldRemove(*field)
+	entitiesFieldRemove(ctx, *field)
 
 	c.Field = append(c.Field[:i], c.Field[i+1:]...)
 
@@ -157,7 +157,7 @@ func (c *CustomFieldsManager) RemoveCustomFieldDef(req *types.RemoveCustomFieldD
 	return body
 }
 
-func (c *CustomFieldsManager) RenameCustomFieldDef(req *types.RenameCustomFieldDef) soap.HasFault {
+func (c *CustomFieldsManager) RenameCustomFieldDef(ctx *Context, req *types.RenameCustomFieldDef) soap.HasFault {
 	body := &methods.RenameCustomFieldDefBody{}
 
 	_, field := c.findByKey(req.Key)
@@ -168,7 +168,7 @@ func (c *CustomFieldsManager) RenameCustomFieldDef(req *types.RenameCustomFieldD
 
 	field.Name = req.Name
 
-	entitiesFieldRename(*field)
+	entitiesFieldRename(ctx, *field)
 
 	body.Res = &types.RenameCustomFieldDefResponse{}
 	return body
diff --git a/simulator/datacenter.go b/simulator/datacenter.go
index 10d0f199..e4294175 100644
--- a/simulator/datacenter.go
+++ b/simulator/datacenter.go
@@ -50,8 +50,8 @@ func NewDatacenter(ctx *Context, f *mo.Folder) *Datacenter {
 	return dc
 }
 
-func (dc *Datacenter) RenameTask(r *types.Rename_Task) soap.HasFault {
-	return RenameTask(dc, r)
+func (dc *Datacenter) RenameTask(ctx *Context, r *types.Rename_Task) soap.HasFault {
+	return RenameTask(ctx, dc, r)
 }
 
 // Create Datacenter Folders.
@@ -155,7 +155,7 @@ func (dc *Datacenter) PowerOnMultiVMTask(ctx *Context, req *types.PowerOnMultiVM
 
 		for _, ref := range req.Vm {
 			vm := Map.Get(ref).(*VirtualMachine)
-			Map.WithLock(vm, func() {
+			ctx.WithLock(vm, func() {
 				vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{})
 			})
 		}
@@ -165,7 +165,7 @@ func (dc *Datacenter) PowerOnMultiVMTask(ctx *Context, req *types.PowerOnMultiVM
 
 	return &methods.PowerOnMultiVM_TaskBody{
 		Res: &types.PowerOnMultiVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -192,7 +192,7 @@ func (d *Datacenter) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/datastore.go b/simulator/datastore.go
index 1a6318f7..310130c9 100644
--- a/simulator/datastore.go
+++ b/simulator/datastore.go
@@ -93,9 +93,9 @@ func (ds *Datastore) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 
 		for _, mount := range ds.Host {
 			host := Map.Get(mount.Key).(*HostSystem)
-			Map.RemoveReference(host, &host.Datastore, ds.Self)
+			Map.RemoveReference(ctx, host, &host.Datastore, ds.Self)
 			parent := hostParent(&host.HostSystem)
-			Map.RemoveReference(parent, &parent.Datastore, ds.Self)
+			Map.RemoveReference(ctx, parent, &parent.Datastore, ds.Self)
 		}
 
 		p, _ := asFolderMO(Map.Get(*ds.Parent))
@@ -106,7 +106,7 @@ func (ds *Datastore) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/dvs.go b/simulator/dvs.go
index a827f057..9e32752d 100644
--- a/simulator/dvs.go
+++ b/simulator/dvs.go
@@ -132,7 +132,7 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
 				pg.Host = append(pg.Host, h)
 
 				host := Map.Get(h).(*HostSystem)
-				Map.AppendReference(host, &host.Network, pg.Reference())
+				Map.AppendReference(ctx, host, &host.Network, pg.Reference())
 
 				parent := Map.Get(*host.HostSystem.Parent)
 				computeNetworks := append(hostParent(&host.HostSystem).Network, pg.Reference())
@@ -152,12 +152,12 @@ func (s *DistributedVirtualSwitch) AddDVPortgroupTask(ctx *Context, c *types.Add
 
 	return &methods.AddDVPortgroup_TaskBody{
 		Res: &types.AddDVPortgroup_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_Task) soap.HasFault {
+func (s *DistributedVirtualSwitch) ReconfigureDvsTask(ctx *Context, req *types.ReconfigureDvs_Task) soap.HasFault {
 	task := CreateTask(s, "reconfigureDvs", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		spec := req.Spec.GetDVSConfigSpec()
 
@@ -229,7 +229,7 @@ func (s *DistributedVirtualSwitch) ReconfigureDvsTask(req *types.ReconfigureDvs_
 
 	return &methods.ReconfigureDvs_TaskBody{
 		Res: &types.ReconfigureDvs_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -251,7 +251,7 @@ func (s *DistributedVirtualSwitch) DestroyTask(ctx *Context, req *types.Destroy_
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/entity.go b/simulator/entity.go
index a18c7845..7144298f 100644
--- a/simulator/entity.go
+++ b/simulator/entity.go
@@ -23,7 +23,7 @@ import (
 	"github.com/vmware/govmomi/vim25/types"
 )
 
-func RenameTask(e mo.Entity, r *types.Rename_Task) soap.HasFault {
+func RenameTask(ctx *Context, e mo.Entity, r *types.Rename_Task) soap.HasFault {
 	task := CreateTask(e, "rename", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		obj := Map.Get(r.This).(mo.Entity).Entity()
 
@@ -40,7 +40,7 @@ func RenameTask(e mo.Entity, r *types.Rename_Task) soap.HasFault {
 
 	return &methods.Rename_TaskBody{
 		Res: &types.Rename_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/event_manager.go b/simulator/event_manager.go
index a963fd67..91f1d209 100644
--- a/simulator/event_manager.go
+++ b/simulator/event_manager.go
@@ -475,7 +475,7 @@ func (c *EventHistoryCollector) ReadPreviousEvents(ctx *Context, req *types.Read
 }
 
 func (c *EventHistoryCollector) DestroyCollector(ctx *Context, req *types.DestroyCollector) soap.HasFault {
-	ctx.Session.Remove(req.This)
+	ctx.Session.Remove(ctx, req.This)
 
 	ctx.WithLock(c.m, func() {
 		delete(c.m.collectors, req.This)
diff --git a/simulator/file_manager.go b/simulator/file_manager.go
index 831b1b87..161ebf73 100644
--- a/simulator/file_manager.go
+++ b/simulator/file_manager.go
@@ -126,14 +126,14 @@ func (f *FileManager) deleteDatastoreFile(req *types.DeleteDatastoreFile_Task) t
 	return nil
 }
 
-func (f *FileManager) DeleteDatastoreFileTask(req *types.DeleteDatastoreFile_Task) soap.HasFault {
+func (f *FileManager) DeleteDatastoreFileTask(ctx *Context, req *types.DeleteDatastoreFile_Task) soap.HasFault {
 	task := CreateTask(f, "deleteDatastoreFile", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, f.deleteDatastoreFile(req)
 	})
 
 	return &methods.DeleteDatastoreFile_TaskBody{
 		Res: &types.DeleteDatastoreFile_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -190,14 +190,14 @@ func (f *FileManager) moveDatastoreFile(req *types.MoveDatastoreFile_Task) types
 	return nil
 }
 
-func (f *FileManager) MoveDatastoreFileTask(req *types.MoveDatastoreFile_Task) soap.HasFault {
+func (f *FileManager) MoveDatastoreFileTask(ctx *Context, req *types.MoveDatastoreFile_Task) soap.HasFault {
 	task := CreateTask(f, "moveDatastoreFile", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, f.moveDatastoreFile(req)
 	})
 
 	return &methods.MoveDatastoreFile_TaskBody{
 		Res: &types.MoveDatastoreFile_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -239,14 +239,14 @@ func (f *FileManager) copyDatastoreFile(req *types.CopyDatastoreFile_Task) types
 	return nil
 }
 
-func (f *FileManager) CopyDatastoreFileTask(req *types.CopyDatastoreFile_Task) soap.HasFault {
+func (f *FileManager) CopyDatastoreFileTask(ctx *Context, req *types.CopyDatastoreFile_Task) soap.HasFault {
 	task := CreateTask(f, "copyDatastoreFile", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, f.copyDatastoreFile(req)
 	})
 
 	return &methods.CopyDatastoreFile_TaskBody{
 		Res: &types.CopyDatastoreFile_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/folder.go b/simulator/folder.go
index b6a0bd5a..a0a4ecc7 100644
--- a/simulator/folder.go
+++ b/simulator/folder.go
@@ -51,7 +51,7 @@ func folderEventArgument(f *mo.Folder) types.FolderEventArgument {
 }
 
 // update references when objects are added/removed from a Folder
-func folderUpdate(f *mo.Folder, o mo.Reference, u func(mo.Reference, *[]types.ManagedObjectReference, types.ManagedObjectReference)) {
+func folderUpdate(ctx *Context, f *mo.Folder, o mo.Reference, u func(*Context, mo.Reference, *[]types.ManagedObjectReference, types.ManagedObjectReference)) {
 	ref := o.Reference()
 
 	if f.Parent == nil {
@@ -67,9 +67,9 @@ func folderUpdate(f *mo.Folder, o mo.Reference, u func(mo.Reference, *[]types.Ma
 
 	switch ref.Type {
 	case "Network", "DistributedVirtualSwitch", "DistributedVirtualPortgroup":
-		u(dc, &dc.Network, ref)
+		u(ctx, dc, &dc.Network, ref)
 	case "Datastore":
-		u(dc, &dc.Datastore, ref)
+		u(ctx, dc, &dc.Datastore, ref)
 	}
 }
 
@@ -90,7 +90,7 @@ func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
 		f.ChildEntity = append(f.ChildEntity, Map.reference(o))
 		Map.PutEntity(f, o)
 
-		folderUpdate(f, o, Map.AddReference)
+		folderUpdate(ctx, f, o, Map.AddReference)
 
 		ctx.WithLock(o, func() {
 			switch e := o.(type) {
@@ -106,12 +106,12 @@ func folderPutChild(ctx *Context, f *mo.Folder, o mo.Entity) {
 }
 
 func folderRemoveChild(ctx *Context, f *mo.Folder, o mo.Reference) {
-	Map.Remove(o.Reference())
+	Map.Remove(ctx, o.Reference())
 
 	ctx.WithLock(f, func() {
 		RemoveReference(&f.ChildEntity, o.Reference())
 
-		folderUpdate(f, o, Map.RemoveReference)
+		folderUpdate(ctx, f, o, Map.RemoveReference)
 	})
 }
 
@@ -130,7 +130,7 @@ func (f *Folder) typeNotSupported() *soap.Fault {
 
 // AddOpaqueNetwork adds an OpaqueNetwork type to the inventory, with default backing to that of an nsx.LogicalSwitch.
 // The vSphere API does not have a method to add this directly, so it must either be called directly or via Model.OpaqueNetwork setting.
-func (f *Folder) AddOpaqueNetwork(summary types.OpaqueNetworkSummary) error {
+func (f *Folder) AddOpaqueNetwork(ctx *Context, summary types.OpaqueNetworkSummary) error {
 	if !folderHasChildType(&f.Folder, "Network") {
 		return errors.New("not a network folder")
 	}
@@ -155,7 +155,7 @@ func (f *Folder) AddOpaqueNetwork(summary types.OpaqueNetworkSummary) error {
 	net.Network.Name = summary.Name
 	net.Summary = &summary
 
-	folderPutChild(internalContext, &f.Folder, net)
+	folderPutChild(ctx, &f.Folder, net)
 
 	return nil
 }
@@ -184,7 +184,7 @@ func (f *Folder) AddStandaloneHostTask(ctx *Context, a *types.AddStandaloneHost_
 
 	if folderHasChildType(&f.Folder, "ComputeResource") && folderHasChildType(&f.Folder, "Folder") {
 		r.Res = &types.AddStandaloneHost_TaskResponse{
-			Returnval: NewTask(&addStandaloneHost{f, ctx, a}).Run(),
+			Returnval: NewTask(&addStandaloneHost{f, ctx, a}).Run(ctx),
 		}
 	} else {
 		r.Fault_ = f.typeNotSupported()
@@ -262,10 +262,22 @@ func (f *Folder) CreateStoragePod(ctx *Context, c *types.CreateStoragePod) soap.
 }
 
 func (p *StoragePod) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task) soap.HasFault {
-	f := &Folder{Folder: p.Folder}
-	res := f.MoveIntoFolderTask(ctx, c)
-	p.ChildEntity = append(p.ChildEntity, f.ChildEntity...)
-	return res
+	task := CreateTask(p, "moveIntoFolder", func(*Task) (types.AnyType, types.BaseMethodFault) {
+		f := &Folder{Folder: p.Folder}
+		id := f.MoveIntoFolderTask(ctx, c).(*methods.MoveIntoFolder_TaskBody).Res.Returnval
+		ftask := Map.Get(id).(*Task)
+		ftask.wait()
+		if ftask.Info.Error != nil {
+			return nil, ftask.Info.Error.Fault
+		}
+		p.ChildEntity = append(p.ChildEntity, f.ChildEntity...)
+		return nil, nil
+	})
+	return &methods.MoveIntoFolder_TaskBody{
+		Res: &types.MoveIntoFolder_TaskResponse{
+			Returnval: task.Run(ctx),
+		},
+	}
 }
 
 func (f *Folder) CreateDatacenter(ctx *Context, c *types.CreateDatacenter) soap.HasFault {
@@ -355,7 +367,7 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 		pool := Map.Get(c.req.Pool).(mo.Entity)
 		cr := Map.getEntityComputeResource(pool)
 
-		Map.WithLock(cr, func() {
+		c.ctx.WithLock(cr, func() {
 			var hosts []types.ManagedObjectReference
 			switch cr := cr.(type) {
 			case *mo.ComputeResource:
@@ -384,19 +396,19 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 	vm.Summary.Config.VmPathName = vm.Config.Files.VmPathName
 	vm.Summary.Runtime.Host = vm.Runtime.Host
 
-	err = vm.create(&c.req.Config, c.register)
+	err = vm.create(c.ctx, &c.req.Config, c.register)
 	if err != nil {
 		folderRemoveChild(c.ctx, &c.Folder.Folder, vm)
 		return nil, err
 	}
 
 	host := Map.Get(*vm.Runtime.Host).(*HostSystem)
-	Map.AppendReference(host, &host.Vm, vm.Self)
+	Map.AppendReference(c.ctx, host, &host.Vm, vm.Self)
 	vm.EnvironmentBrowser = *hostParent(&host.HostSystem).EnvironmentBrowser
 
 	for i := range vm.Datastore {
 		ds := Map.Get(vm.Datastore[i]).(*Datastore)
-		Map.AppendReference(ds, &ds.Vm, vm.Self)
+		Map.AppendReference(c.ctx, ds, &ds.Vm, vm.Self)
 	}
 
 	pool := Map.Get(*vm.ResourcePool)
@@ -441,7 +453,7 @@ func (c *createVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 func (f *Folder) CreateVMTask(ctx *Context, c *types.CreateVM_Task) soap.HasFault {
 	return &methods.CreateVM_TaskBody{
 		Res: &types.CreateVM_TaskResponse{
-			Returnval: NewTask(&createVM{f, ctx, c, false}).Run(),
+			Returnval: NewTask(&createVM{f, ctx, c, false}).Run(ctx),
 		},
 	}
 }
@@ -512,7 +524,7 @@ func (c *registerVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 		},
 	})
 
-	create.Run()
+	create.RunBlocking(c.ctx)
 
 	if create.Info.Error != nil {
 		return nil, create.Info.Error.Fault
@@ -524,7 +536,7 @@ func (c *registerVM) Run(task *Task) (types.AnyType, types.BaseMethodFault) {
 func (f *Folder) RegisterVMTask(ctx *Context, c *types.RegisterVM_Task) soap.HasFault {
 	return &methods.RegisterVM_TaskBody{
 		Res: &types.RegisterVM_TaskResponse{
-			Returnval: NewTask(&registerVM{f, ctx, c}).Run(),
+			Returnval: NewTask(&registerVM{f, ctx, c}).Run(ctx),
 		},
 	}
 }
@@ -549,7 +561,7 @@ func (f *Folder) MoveIntoFolderTask(ctx *Context, c *types.MoveIntoFolder_Task)
 
 	return &methods.MoveIntoFolder_TaskBody{
 		Res: &types.MoveIntoFolder_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -646,13 +658,13 @@ func (f *Folder) CreateDVSTask(ctx *Context, req *types.CreateDVS_Task) soap.Has
 
 	return &methods.CreateDVS_TaskBody{
 		Res: &types.CreateDVS_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (f *Folder) RenameTask(r *types.Rename_Task) soap.HasFault {
-	return RenameTask(f, r)
+func (f *Folder) RenameTask(ctx *Context, r *types.Rename_Task) soap.HasFault {
+	return RenameTask(ctx, f, r)
 }
 
 func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
@@ -670,12 +682,13 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 			}
 
 			var fault types.BaseMethodFault
-			Map.WithLock(obj, func() {
+			ctx.WithLock(obj, func() {
 				id := obj.DestroyTask(&types.Destroy_Task{
 					This: c,
 				}).(*methods.Destroy_TaskBody).Res.Returnval
 
 				t := Map.Get(id).(*Task)
+				t.wait()
 				if t.Info.Error != nil {
 					fault = t.Info.Error.Fault // For example, can't destroy a powered on VM
 				}
@@ -692,7 +705,7 @@ func (f *Folder) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFaul
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/host_datastore_browser.go b/simulator/host_datastore_browser.go
index adc1ec67..81f71be3 100644
--- a/simulator/host_datastore_browser.go
+++ b/simulator/host_datastore_browser.go
@@ -195,8 +195,12 @@ func (s *searchDatastore) Run(task *Task) (types.AnyType, types.BaseMethodFault)
 	}
 
 	ds := ref.(*Datastore)
-	task.Info.Entity = &ds.Self // TODO: CreateTask() should require mo.Entity, rather than mo.Reference
-	task.Info.EntityName = ds.Name
+
+	isolatedLockContext := &Context{} // we don't need/want to share the task lock
+	Map.WithLock(isolatedLockContext, task, func() {
+		task.Info.Entity = &ds.Self // TODO: CreateTask() should require mo.Entity, rather than mo.Reference
+		task.Info.EntityName = ds.Name
+	})
 
 	dir := path.Join(ds.Info.GetDatastoreInfo().Url, p.Path)
 
@@ -222,7 +226,7 @@ func (s *searchDatastore) Run(task *Task) (types.AnyType, types.BaseMethodFault)
 	return s.res[0], nil
 }
 
-func (b *HostDatastoreBrowser) SearchDatastoreTask(s *types.SearchDatastore_Task) soap.HasFault {
+func (b *HostDatastoreBrowser) SearchDatastoreTask(ctx *Context, s *types.SearchDatastore_Task) soap.HasFault {
 	task := NewTask(&searchDatastore{
 		HostDatastoreBrowser: b,
 		DatastorePath:        s.DatastorePath,
@@ -231,12 +235,12 @@ func (b *HostDatastoreBrowser) SearchDatastoreTask(s *types.SearchDatastore_Task
 
 	return &methods.SearchDatastore_TaskBody{
 		Res: &types.SearchDatastore_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (b *HostDatastoreBrowser) SearchDatastoreSubFoldersTask(s *types.SearchDatastoreSubFolders_Task) soap.HasFault {
+func (b *HostDatastoreBrowser) SearchDatastoreSubFoldersTask(ctx *Context, s *types.SearchDatastoreSubFolders_Task) soap.HasFault {
 	task := NewTask(&searchDatastore{
 		HostDatastoreBrowser: b,
 		DatastorePath:        s.DatastorePath,
@@ -246,7 +250,7 @@ func (b *HostDatastoreBrowser) SearchDatastoreSubFoldersTask(s *types.SearchData
 
 	return &methods.SearchDatastoreSubFolders_TaskBody{
 		Res: &types.SearchDatastoreSubFolders_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/host_datastore_system.go b/simulator/host_datastore_system.go
index a4052879..dd62f557 100644
--- a/simulator/host_datastore_system.go
+++ b/simulator/host_datastore_system.go
@@ -81,7 +81,7 @@ func (dss *HostDatastoreSystem) add(ctx *Context, ds *Datastore) *soap.Fault {
 	dss.Datastore = append(dss.Datastore, ds.Self)
 	dss.Host.Datastore = dss.Datastore
 	parent := hostParent(dss.Host)
-	Map.AddReference(parent, &parent.Datastore, ds.Self)
+	Map.AddReference(ctx, parent, &parent.Datastore, ds.Self)
 
 	browser := &HostDatastoreBrowser{}
 	browser.Datastore = dss.Datastore
diff --git a/simulator/host_system.go b/simulator/host_system.go
index 28c4b3e6..b1591181 100644
--- a/simulator/host_system.go
+++ b/simulator/host_system.go
@@ -237,12 +237,12 @@ func (h *HostSystem) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.Has
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (h *HostSystem) EnterMaintenanceModeTask(spec *types.EnterMaintenanceMode_Task) soap.HasFault {
+func (h *HostSystem) EnterMaintenanceModeTask(ctx *Context, spec *types.EnterMaintenanceMode_Task) soap.HasFault {
 	task := CreateTask(h, "enterMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		h.Runtime.InMaintenanceMode = true
 		return nil, nil
@@ -250,12 +250,12 @@ func (h *HostSystem) EnterMaintenanceModeTask(spec *types.EnterMaintenanceMode_T
 
 	return &methods.EnterMaintenanceMode_TaskBody{
 		Res: &types.EnterMaintenanceMode_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (h *HostSystem) ExitMaintenanceModeTask(spec *types.ExitMaintenanceMode_Task) soap.HasFault {
+func (h *HostSystem) ExitMaintenanceModeTask(ctx *Context, spec *types.ExitMaintenanceMode_Task) soap.HasFault {
 	task := CreateTask(h, "exitMaintenanceMode", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		h.Runtime.InMaintenanceMode = false
 		return nil, nil
@@ -263,7 +263,7 @@ func (h *HostSystem) ExitMaintenanceModeTask(spec *types.ExitMaintenanceMode_Tas
 
 	return &methods.ExitMaintenanceMode_TaskBody{
 		Res: &types.ExitMaintenanceMode_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/http_nfc_lease.go b/simulator/http_nfc_lease.go
index 886be5e8..e7357f11 100644
--- a/simulator/http_nfc_lease.go
+++ b/simulator/http_nfc_lease.go
@@ -111,7 +111,7 @@ func NewHttpNfcLease(ctx *Context, entity types.ManagedObjectReference) *HttpNfc
 }
 
 func (l *HttpNfcLease) HttpNfcLeaseComplete(ctx *Context, req *types.HttpNfcLeaseComplete) soap.HasFault {
-	ctx.Session.Remove(req.This)
+	ctx.Session.Remove(ctx, req.This)
 	nfcLease.Delete(req.This)
 
 	return &methods.HttpNfcLeaseCompleteBody{
@@ -120,7 +120,7 @@ func (l *HttpNfcLease) HttpNfcLeaseComplete(ctx *Context, req *types.HttpNfcLeas
 }
 
 func (l *HttpNfcLease) HttpNfcLeaseAbort(ctx *Context, req *types.HttpNfcLeaseAbort) soap.HasFault {
-	ctx.Session.Remove(req.This)
+	ctx.Session.Remove(ctx, req.This)
 	nfcLease.Delete(req.This)
 
 	return &methods.HttpNfcLeaseAbortBody{
diff --git a/simulator/internal/object_lock.go b/simulator/internal/object_lock.go
new file mode 100644
index 00000000..8c80f817
--- /dev/null
+++ b/simulator/internal/object_lock.go
@@ -0,0 +1,86 @@
+package internal
+
+import (
+	"fmt"
+	"sync"
+)
+
+// SharedLockingContext is used to identify when locks can be shared. In
+// practice, simulator code uses the simulator.Context for a requst, but in
+// principle this could be anything.
+type SharedLockingContext interface{}
+
+// ObjectLock implements a basic "reference-counted" mutex, where a single
+// SharedLockingContext can "share" the lock across code paths or child tasks.
+type ObjectLock struct {
+	lock sync.Locker
+
+	stateLock sync.Mutex
+	heldBy    SharedLockingContext
+	count     int64
+}
+
+// NewObjectLock creates a new ObjectLock. Pass new(sync.Mutex) if you don't
+// have a custom sync.Locker.
+func NewObjectLock(lock sync.Locker) *ObjectLock {
+	return &ObjectLock{
+		lock: lock,
+	}
+}
+
+// try returns true if the lock has been acquired; false otherwise
+func (l *ObjectLock) try(onBehalfOf SharedLockingContext) bool {
+	l.stateLock.Lock()
+	defer l.stateLock.Unlock()
+
+	if l.heldBy == onBehalfOf {
+		l.count = l.count + 1
+		return true
+	}
+
+	if l.heldBy == nil {
+		// we expect no contention for this lock (unless the object has a custom Locker)
+		l.lock.Lock()
+		l.count = 1
+		l.heldBy = onBehalfOf
+		return true
+	}
+
+	return false
+}
+
+// wait returns when there's a chance that try() might succeed.
+// It is intended to be better than busy-waiting or sleeping.
+func (l *ObjectLock) wait() {
+	l.lock.Lock()
+	l.lock.Unlock()
+}
+
+// Release decrements the reference count. The caller should pass their
+// context, which is used to sanity check that the Unlock() call is valid. If
+// this is the last reference to the lock for this SharedLockingContext, the lock
+// is Unlocked and can be acquired by another SharedLockingContext.
+func (l *ObjectLock) Release(onBehalfOf SharedLockingContext) {
+	l.stateLock.Lock()
+	defer l.stateLock.Unlock()
+	if l.heldBy != onBehalfOf {
+		panic(fmt.Sprintf("Attempt to unlock on behalf of %#v, but is held by %#v", onBehalfOf, l.heldBy))
+	}
+	l.count = l.count - 1
+	if l.count == 0 {
+		l.heldBy = nil
+		l.lock.Unlock()
+	}
+}
+
+// Acquire blocks until it can acquire the lock for onBehalfOf
+func (l *ObjectLock) Acquire(onBehalfOf SharedLockingContext) {
+	acquired := false
+	for !acquired {
+		if l.try(onBehalfOf) {
+			return
+		} else {
+			l.wait()
+		}
+	}
+}
diff --git a/simulator/model.go b/simulator/model.go
index 3cdd6668..16fbe26b 100644
--- a/simulator/model.go
+++ b/simulator/model.go
@@ -22,11 +22,13 @@ import (
 	"fmt"
 	"io/ioutil"
 	"log"
+	"math/rand"
 	"os"
 	"path"
 	"path/filepath"
 	"reflect"
 	"strings"
+	"time"
 
 	"github.com/google/uuid"
 	"github.com/vmware/govmomi"
@@ -307,7 +309,7 @@ func (m *Model) resolveReferences(ctx *Context) error {
 	if !ok {
 		// Need to have at least 1 Datacenter
 		root := Map.Get(Map.content().RootFolder).(*Folder)
-		ref := root.CreateDatacenter(internalContext, &types.CreateDatacenter{
+		ref := root.CreateDatacenter(ctx, &types.CreateDatacenter{
 			This: root.Self,
 			Name: "DC0",
 		}).(*methods.CreateDatacenterBody).Res.Returnval
@@ -381,9 +383,27 @@ func (m *Model) loadMethod(obj mo.Reference, dir string) error {
 	return nil
 }
 
+// When simulator code needs to call other simulator code, it typically passes whatever
+// context is associated with the request it's servicing.
+// Model code isn't servicing a request, but still needs a context, so we spoof
+// one for the purposes of calling simulator code.
+// Test code also tends to do this.
+func SpoofContext() *Context {
+	return &Context{
+		Context: context.Background(),
+		Session: &Session{
+			UserSession: types.UserSession{
+				Key: uuid.New().String(),
+			},
+			Registry: NewRegistry(),
+		},
+		Map: Map,
+	}
+}
+
 // Load Model from the given directory, as created by the 'govc object.save' command.
 func (m *Model) Load(dir string) error {
-	ctx := internalContext
+	ctx := SpoofContext()
 	var s *ServiceInstance
 
 	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
@@ -415,7 +435,7 @@ func (m *Model) Load(dir string) error {
 		}
 
 		if s == nil {
-			s = NewServiceInstance(m.ServiceContent, m.RootFolder)
+			s = NewServiceInstance(ctx, m.ServiceContent, m.RootFolder)
 		}
 
 		obj, err := loadObject(content)
@@ -443,8 +463,8 @@ func (m *Model) Load(dir string) error {
 
 // Create populates the Model with the given ModelConfig
 func (m *Model) Create() error {
-	ctx := internalContext
-	m.Service = New(NewServiceInstance(m.ServiceContent, m.RootFolder))
+	ctx := SpoofContext()
+	m.Service = New(NewServiceInstance(ctx, m.ServiceContent, m.RootFolder))
 
 	client := m.Service.client
 	root := object.NewRootFolder(client)
@@ -487,7 +507,8 @@ func (m *Model) Create() error {
 				}},
 			}
 
-			_, _ = dvs.Reconfigure(ctx, config)
+			task, _ = dvs.Reconfigure(ctx, config)
+			_, _ = task.WaitForResult(context.Background(), nil)
 		}
 
 		return host, nil
@@ -541,7 +562,8 @@ func (m *Model) Create() error {
 				vm := object.NewVirtualMachine(client, info.Result.(types.ManagedObjectReference))
 
 				if m.Autostart {
-					_, _ = vm.PowerOn(ctx)
+					task, _ = vm.PowerOn(ctx)
+					_, _ = task.WaitForResult(ctx, nil)
 				}
 			}
 
@@ -665,7 +687,7 @@ func (m *Model) Create() error {
 		for i := 0; i < m.OpaqueNetwork; i++ {
 			var summary types.OpaqueNetworkSummary
 			summary.Name = m.fmtName(dcName+"_NSX", i)
-			err := networkFolder.AddOpaqueNetwork(summary)
+			err := networkFolder.AddOpaqueNetwork(ctx, summary)
 			if err != nil {
 				return err
 			}
@@ -811,11 +833,13 @@ func (m *Model) createLocalDatastore(dc string, name string, hosts []*object.Hos
 // Remove cleans up items created by the Model, such as local datastore directories
 func (m *Model) Remove() {
 	// Remove associated vm containers, if any
+	Map.m.Lock()
 	for _, obj := range Map.objects {
 		if vm, ok := obj.(*VirtualMachine); ok {
 			vm.run.remove(vm)
 		}
 	}
+	Map.m.Unlock()
 
 	for _, dir := range m.dirs {
 		_ = os.RemoveAll(dir)
@@ -874,3 +898,21 @@ func Test(f func(context.Context, *vim25.Client), model ...*Model) {
 		return nil
 	}, model...)
 }
+
+// delay sleeps according to DelayConfig. If no delay specified, returns immediately.
+func (dc *DelayConfig) delay(method string) {
+	d := 0
+	if dc.Delay > 0 {
+		d = dc.Delay
+	}
+	if md, ok := dc.MethodDelay[method]; ok {
+		d += md
+	}
+	if dc.DelayJitter > 0 {
+		d += int(rand.NormFloat64() * dc.DelayJitter * float64(d))
+	}
+	if d > 0 {
+		//fmt.Printf("Delaying method %s %d ms\n", method, d)
+		time.Sleep(time.Duration(d) * time.Millisecond)
+	}
+}
diff --git a/simulator/portgroup.go b/simulator/portgroup.go
index 17832742..9f970539 100644
--- a/simulator/portgroup.go
+++ b/simulator/portgroup.go
@@ -27,7 +27,7 @@ type DistributedVirtualPortgroup struct {
 	mo.DistributedVirtualPortgroup
 }
 
-func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(req *types.ReconfigureDVPortgroup_Task) soap.HasFault {
+func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(ctx *Context, req *types.ReconfigureDVPortgroup_Task) soap.HasFault {
 	task := CreateTask(s, "reconfigureDvPortgroup", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		s.Config.DefaultPortConfig = req.Spec.DefaultPortConfig
 		s.Config.NumPorts = req.Spec.NumPorts
@@ -47,7 +47,7 @@ func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(req *types.Reco
 
 	return &methods.ReconfigureDVPortgroup_TaskBody{
 		Res: &types.ReconfigureDVPortgroup_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -55,8 +55,8 @@ func (s *DistributedVirtualPortgroup) ReconfigureDVPortgroupTask(req *types.Reco
 func (s *DistributedVirtualPortgroup) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.HasFault {
 	task := CreateTask(s, "destroy", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		vswitch := Map.Get(*s.Config.DistributedVirtualSwitch).(*DistributedVirtualSwitch)
-		Map.RemoveReference(vswitch, &vswitch.Portgroup, s.Reference())
-		Map.removeString(vswitch, &vswitch.Summary.PortgroupName, s.Name)
+		Map.RemoveReference(ctx, vswitch, &vswitch.Portgroup, s.Reference())
+		Map.removeString(ctx, vswitch, &vswitch.Summary.PortgroupName, s.Name)
 
 		f := Map.getEntityParent(vswitch, "Folder").(*Folder)
 		folderRemoveChild(ctx, &f.Folder, s.Reference())
@@ -66,7 +66,7 @@ func (s *DistributedVirtualPortgroup) DestroyTask(ctx *Context, req *types.Destr
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 
diff --git a/simulator/property_collector.go b/simulator/property_collector.go
index b6c16c54..759cf512 100644
--- a/simulator/property_collector.go
+++ b/simulator/property_collector.go
@@ -485,8 +485,8 @@ func (pc *PropertyCollector) DestroyPropertyCollector(ctx *Context, c *types.Des
 		filter.DestroyPropertyFilter(ctx, &types.DestroyPropertyFilter{This: ref})
 	}
 
-	ctx.Session.Remove(c.This)
-	ctx.Map.Remove(c.This)
+	ctx.Session.Remove(ctx, c.This)
+	ctx.Map.Remove(ctx, c.This)
 
 	body.Res = &types.DestroyPropertyCollectorResponse{}
 
@@ -579,7 +579,7 @@ func (pc *PropertyCollector) UpdateObject(o mo.Reference, changes []types.Proper
 	})
 }
 
-func (pc *PropertyCollector) RemoveObject(ref types.ManagedObjectReference) {
+func (pc *PropertyCollector) RemoveObject(_ *Context, ref types.ManagedObjectReference) {
 	pc.update(types.ObjectUpdate{
 		Obj:       ref,
 		Kind:      types.ObjectUpdateKindLeave,
@@ -673,7 +673,7 @@ func (pc *PropertyCollector) WaitForUpdatesEx(ctx *Context, r *types.WaitForUpda
 		return body
 	}
 
-	ticker := time.NewTicker(250 * time.Millisecond) // allow for updates to accumulate
+	ticker := time.NewTicker(20 * time.Millisecond) // allow for updates to accumulate
 	defer ticker.Stop()
 	// Start the wait loop, returning on one of:
 	// - Client calls CancelWaitForUpdates
diff --git a/simulator/property_filter.go b/simulator/property_filter.go
index b7a4af5d..b84c85e7 100644
--- a/simulator/property_filter.go
+++ b/simulator/property_filter.go
@@ -38,7 +38,7 @@ func (f *PropertyFilter) DestroyPropertyFilter(ctx *Context, c *types.DestroyPro
 
 	RemoveReference(&f.pc.Filter, c.This)
 
-	ctx.Session.Remove(c.This)
+	ctx.Session.Remove(ctx, c.This)
 
 	body.Res = &types.DestroyPropertyFilterResponse{}
 
diff --git a/simulator/registry.go b/simulator/registry.go
index 899e6bfd..bd41dc9d 100644
--- a/simulator/registry.go
+++ b/simulator/registry.go
@@ -26,6 +26,7 @@ import (
 	"sync"
 	"sync/atomic"
 
+	"github.com/vmware/govmomi/simulator/internal"
 	"github.com/vmware/govmomi/vim25"
 	"github.com/vmware/govmomi/vim25/mo"
 	"github.com/vmware/govmomi/vim25/soap"
@@ -54,7 +55,7 @@ type RegisterObject interface {
 	mo.Reference
 	PutObject(mo.Reference)
 	UpdateObject(mo.Reference, []types.PropertyChange)
-	RemoveObject(types.ManagedObjectReference)
+	RemoveObject(*Context, types.ManagedObjectReference)
 }
 
 // Registry manages a map of mo.Reference objects
@@ -63,7 +64,7 @@ type Registry struct {
 	m        sync.Mutex
 	objects  map[types.ManagedObjectReference]mo.Reference
 	handlers map[types.ManagedObjectReference]RegisterObject
-	locks    map[types.ManagedObjectReference]sync.Locker
+	locks    map[types.ManagedObjectReference]*internal.ObjectLock
 
 	Namespace string
 	Path      string
@@ -84,7 +85,7 @@ func NewRegistry() *Registry {
 	r := &Registry{
 		objects:  make(map[types.ManagedObjectReference]mo.Reference),
 		handlers: make(map[types.ManagedObjectReference]RegisterObject),
-		locks:    make(map[types.ManagedObjectReference]sync.Locker),
+		locks:    make(map[types.ManagedObjectReference]*internal.ObjectLock),
 
 		Namespace: vim25.Namespace,
 		Path:      vim25.Path,
@@ -259,9 +260,9 @@ func (r *Registry) Put(item mo.Reference) mo.Reference {
 }
 
 // Remove removes an object from the Registry.
-func (r *Registry) Remove(item types.ManagedObjectReference) {
+func (r *Registry) Remove(ctx *Context, item types.ManagedObjectReference) {
 	r.applyHandlers(func(o RegisterObject) {
-		o.RemoveObject(item)
+		o.RemoveObject(ctx, item)
 	})
 
 	r.m.Lock()
@@ -295,6 +296,12 @@ func (r *Registry) Update(obj mo.Reference, changes []types.PropertyChange) {
 	})
 }
 
+func (r *Registry) AtomicUpdate(ctx *Context, obj mo.Reference, changes []types.PropertyChange) {
+	r.WithLock(ctx, obj, func() {
+		r.Update(obj, changes)
+	})
+}
+
 // getEntityParent traverses up the inventory and returns the first object of type kind.
 // If no object of type kind is found, the method will panic when it reaches the
 // inventory root Folder where the Parent field is nil.
@@ -418,15 +425,15 @@ func FindReference(refs []types.ManagedObjectReference, match ...types.ManagedOb
 }
 
 // AppendReference appends the given refs to field.
-func (r *Registry) AppendReference(obj mo.Reference, field *[]types.ManagedObjectReference, ref ...types.ManagedObjectReference) {
-	r.WithLock(obj, func() {
+func (r *Registry) AppendReference(ctx *Context, obj mo.Reference, field *[]types.ManagedObjectReference, ref ...types.ManagedObjectReference) {
+	r.WithLock(ctx, obj, func() {
 		*field = append(*field, ref...)
 	})
 }
 
 // AddReference appends ref to field if not already in the given field.
-func (r *Registry) AddReference(obj mo.Reference, field *[]types.ManagedObjectReference, ref types.ManagedObjectReference) {
-	r.WithLock(obj, func() {
+func (r *Registry) AddReference(ctx *Context, obj mo.Reference, field *[]types.ManagedObjectReference, ref types.ManagedObjectReference) {
+	r.WithLock(ctx, obj, func() {
 		if FindReference(*field, ref) == nil {
 			*field = append(*field, ref)
 		}
@@ -444,14 +451,14 @@ func RemoveReference(field *[]types.ManagedObjectReference, ref types.ManagedObj
 }
 
 // RemoveReference removes ref from the given field.
-func (r *Registry) RemoveReference(obj mo.Reference, field *[]types.ManagedObjectReference, ref types.ManagedObjectReference) {
-	r.WithLock(obj, func() {
+func (r *Registry) RemoveReference(ctx *Context, obj mo.Reference, field *[]types.ManagedObjectReference, ref types.ManagedObjectReference) {
+	r.WithLock(ctx, obj, func() {
 		RemoveReference(field, ref)
 	})
 }
 
-func (r *Registry) removeString(obj mo.Reference, field *[]string, val string) {
-	r.WithLock(obj, func() {
+func (r *Registry) removeString(ctx *Context, obj mo.Reference, field *[]string, val string) {
+	r.WithLock(ctx, obj, func() {
 		for i, name := range *field {
 			if name == val {
 				*field = append((*field)[:i], (*field)[i+1:]...)
@@ -546,7 +553,7 @@ func (r *Registry) MarshalJSON() ([]byte, error) {
 	return json.Marshal(vars)
 }
 
-func (r *Registry) locker(obj mo.Reference) sync.Locker {
+func (r *Registry) locker(obj mo.Reference) *internal.ObjectLock {
 	var ref types.ManagedObjectReference
 
 	switch x := obj.(type) {
@@ -563,13 +570,17 @@ func (r *Registry) locker(obj mo.Reference) sync.Locker {
 	}
 
 	if mu, ok := obj.(sync.Locker); ok {
-		return mu
+		// Objects that opt out of default locking are responsible for
+		// implementing their own lock sharing, if needed. Returning
+		// nil as heldBy means that WithLock will call Lock/Unlock
+		// every time.
+		return internal.NewObjectLock(mu)
 	}
 
 	r.m.Lock()
 	mu, ok := r.locks[ref]
 	if !ok {
-		mu = new(sync.Mutex)
+		mu = internal.NewObjectLock(new(sync.Mutex))
 		r.locks[ref] = mu
 	}
 	r.m.Unlock()
@@ -580,13 +591,29 @@ func (r *Registry) locker(obj mo.Reference) sync.Locker {
 var enableLocker = os.Getenv("VCSIM_LOCKER") != "false"
 
 // WithLock holds a lock for the given object while then given function is run.
-func (r *Registry) WithLock(obj mo.Reference, f func()) {
-	if enableLocker {
-		mu := r.locker(obj)
-		mu.Lock()
-		defer mu.Unlock()
-	}
+func (r *Registry) WithLock(onBehalfOf *Context, obj mo.Reference, f func()) {
+	unlock := r.AcquireLock(onBehalfOf, obj)
 	f()
+	unlock()
+}
+
+// AcquireLock acquires the lock for onBehalfOf then returns. The lock MUST be
+// released by calling the returned function. WithLock should be preferred
+// wherever possible.
+func (r *Registry) AcquireLock(onBehalfOf *Context, obj mo.Reference) func() {
+	if onBehalfOf == nil {
+		panic(fmt.Sprintf("Attempt to lock %v with nil onBehalfOf", obj))
+	}
+
+	if !enableLocker {
+		return func() {}
+	}
+
+	l := r.locker(obj)
+	l.Acquire(onBehalfOf)
+	return func() {
+		l.Release(onBehalfOf)
+	}
 }
 
 // nopLocker can be embedded to opt-out of auto-locking (see Registry.WithLock)
diff --git a/simulator/resource_pool.go b/simulator/resource_pool.go
index 5d7f28cf..05840619 100644
--- a/simulator/resource_pool.go
+++ b/simulator/resource_pool.go
@@ -225,6 +225,8 @@ func (p *ResourcePool) ImportVApp(ctx *Context, req *types.ImportVApp) soap.HasF
 	})
 
 	ctask := Map.Get(res.(*methods.CreateVM_TaskBody).Res.Returnval).(*Task)
+	ctask.wait()
+
 	if ctask.Info.Error != nil {
 		body.Fault_ = Fault("", ctask.Info.Error.Fault)
 		return body
@@ -404,6 +406,7 @@ func (a *VirtualApp) CloneVAppTask(ctx *Context, req *types.CloneVApp_Task) soap
 			})
 
 			ctask := Map.Get(res.(*methods.CloneVM_TaskBody).Res.Returnval).(*Task)
+			ctask.wait()
 			if ctask.Info.Error != nil {
 				return nil, ctask.Info.Error.Fault
 			}
@@ -414,7 +417,7 @@ func (a *VirtualApp) CloneVAppTask(ctx *Context, req *types.CloneVApp_Task) soap
 
 	return &methods.CloneVApp_TaskBody{
 		Res: &types.CloneVApp_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -448,21 +451,21 @@ func (p *ResourcePool) DestroyTask(ctx *Context, req *types.Destroy_Task) soap.H
 		vms := p.ResourcePool.Vm
 		for _, ref := range vms {
 			vm := ctx.Map.Get(ref).(*VirtualMachine)
-			ctx.Map.WithLock(vm, func() { vm.ResourcePool = &parent.Self })
+			ctx.WithLock(vm, func() { vm.ResourcePool = &parent.Self })
 		}
 
 		ctx.WithLock(parent, func() {
 			parent.Vm = append(parent.Vm, vms...)
 		})
 
-		ctx.Map.Remove(req.This)
+		ctx.Map.Remove(ctx, req.This)
 
 		return nil, nil
 	})
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/service_instance.go b/simulator/service_instance.go
index 39145642..29cbea74 100644
--- a/simulator/service_instance.go
+++ b/simulator/service_instance.go
@@ -32,7 +32,7 @@ type ServiceInstance struct {
 	mo.ServiceInstance
 }
 
-func NewServiceInstance(content types.ServiceContent, folder mo.Folder) *ServiceInstance {
+func NewServiceInstance(ctx *Context, content types.ServiceContent, folder mo.Folder) *ServiceInstance {
 	Map = NewRegistry()
 
 	s := &ServiceInstance{}
@@ -46,7 +46,7 @@ func NewServiceInstance(content types.ServiceContent, folder mo.Folder) *Service
 	Map.Put(f)
 
 	if content.About.ApiType == "HostAgent" {
-		CreateDefaultESX(internalContext, f)
+		CreateDefaultESX(ctx, f)
 	} else {
 		content.About.InstanceUuid = uuid.New().String()
 	}
diff --git a/simulator/session_manager.go b/simulator/session_manager.go
index 47a04355..2b5102cb 100644
--- a/simulator/session_manager.go
+++ b/simulator/session_manager.go
@@ -187,7 +187,7 @@ func (s *SessionManager) Logout(ctx *Context, _ *types.Logout) soap.HasFault {
 			continue // don't unregister the PropertyCollector singleton
 		}
 		if _, ok := obj.(RegisterObject); ok {
-			ctx.Map.Remove(ref) // Remove RegisterObject handlers
+			ctx.Map.Remove(ctx, ref) // Remove RegisterObject handlers
 		}
 	}
 
@@ -280,18 +280,6 @@ func (s *SessionManager) AcquireGenericServiceTicket(ticket *types.AcquireGeneri
 	}
 }
 
-// internalContext is the session for use by the in-memory client (Service.RoundTrip)
-var internalContext = &Context{
-	Context: context.Background(),
-	Session: &Session{
-		UserSession: types.UserSession{
-			Key: uuid.New().String(),
-		},
-		Registry: NewRegistry(),
-	},
-	Map: Map,
-}
-
 var invalidLogin = Fault("Login failure", new(types.InvalidLogin))
 
 // Context provides per-request Session management.
@@ -382,13 +370,15 @@ func (c *Context) SetSession(session Session, login bool) {
 }
 
 // WithLock holds a lock for the given object while then given function is run.
+// It will skip locking if this context already holds the given object's lock.
 func (c *Context) WithLock(obj mo.Reference, f func()) {
-	if c.Caller != nil && *c.Caller == obj.Reference() {
-		// Internal method invocation, obj is already locked
-		f()
-		return
-	}
-	Map.WithLock(obj, f)
+	// TODO: This is not always going to be correct. An object should
+	// really be locked by the registry that "owns it", which is not always
+	// Map. This function will need to take the Registry as an additional
+	// argument to accomplish this.
+	// Basic mutex locking will work even if obj doesn't belong to Map, but
+	// if obj implements sync.Locker, that custom locking will not be used.
+	Map.WithLock(c, obj, f)
 }
 
 // postEvent wraps EventManager.PostEvent for internal use, with a lock on the EventManager.
diff --git a/simulator/simulator.go b/simulator/simulator.go
index 42cf9312..08d86b25 100644
--- a/simulator/simulator.go
+++ b/simulator/simulator.go
@@ -28,7 +28,6 @@ import (
 	"io"
 	"io/ioutil"
 	"log"
-	"math/rand"
 	"net"
 	"net/http"
 	"net/url"
@@ -38,8 +37,8 @@ import (
 	"sort"
 	"strconv"
 	"strings"
-	"time"
 
+	"github.com/google/uuid"
 	"github.com/vmware/govmomi/find"
 	"github.com/vmware/govmomi/object"
 	"github.com/vmware/govmomi/simulator/internal"
@@ -189,22 +188,8 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
 	}
 
 	// We have a valid call. Introduce a delay if requested
-	//
 	if s.delay != nil {
-		d := 0
-		if s.delay.Delay > 0 {
-			d = s.delay.Delay
-		}
-		if md, ok := s.delay.MethodDelay[method.Name]; ok {
-			d += md
-		}
-		if s.delay.DelayJitter > 0 {
-			d += int(rand.NormFloat64() * s.delay.DelayJitter * float64(d))
-		}
-		if d > 0 {
-			//fmt.Printf("Delaying method %s %d ms\n", name, d)
-			time.Sleep(time.Duration(d) * time.Millisecond)
-		}
+		s.delay.delay(method.Name)
 	}
 
 	var args, res []reflect.Value
@@ -212,13 +197,21 @@ func (s *Service) call(ctx *Context, method *Method) soap.HasFault {
 		args = append(args, reflect.ValueOf(ctx))
 	}
 	args = append(args, reflect.ValueOf(method.Body))
-	ctx.Map.WithLock(handler, func() {
+	ctx.Map.WithLock(ctx, handler, func() {
 		res = m.Call(args)
 	})
 
 	return res[0].Interface().(soap.HasFault)
 }
 
+// internalSession is the session for use by the in-memory client (Service.RoundTrip)
+var internalSession = &Session{
+	UserSession: types.UserSession{
+		Key: uuid.New().String(),
+	},
+	Registry: NewRegistry(),
+}
+
 // RoundTrip implements the soap.RoundTripper interface in process.
 // Rather than encode/decode SOAP over HTTP, this implementation uses reflection.
 func (s *Service) RoundTrip(ctx context.Context, request, response soap.HasFault) error {
@@ -241,7 +234,7 @@ func (s *Service) RoundTrip(ctx context.Context, request, response soap.HasFault
 	res := s.call(&Context{
 		Map:     Map,
 		Context: ctx,
-		Session: internalContext.Session,
+		Session: internalSession,
 	}, method)
 
 	if err := res.Fault(); err != nil {
@@ -463,7 +456,7 @@ func (s *Service) ServeSDK(w http.ResponseWriter, r *http.Request) {
 		Map:     s.sdk[r.URL.Path],
 		Context: context.Background(),
 	}
-	ctx.Map.WithLock(s.sm, ctx.mapSession)
+	ctx.Map.WithLock(ctx, s.sm, ctx.mapSession)
 
 	var res soap.HasFault
 	var soapBody interface{}
diff --git a/simulator/snapshot.go b/simulator/snapshot.go
index bec373c3..ff72839d 100644
--- a/simulator/snapshot.go
+++ b/simulator/snapshot.go
@@ -114,7 +114,7 @@ func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.Rem
 		var changes []types.PropertyChange
 
 		vm := Map.Get(v.Vm).(*VirtualMachine)
-		Map.WithLock(vm, func() {
+		ctx.WithLock(vm, func() {
 			if vm.Snapshot.CurrentSnapshot != nil && *vm.Snapshot.CurrentSnapshot == req.This {
 				parent := findParentSnapshotInTree(vm.Snapshot.RootSnapshotList, req.This)
 				changes = append(changes, types.PropertyChange{Name: "snapshot.currentSnapshot", Val: parent})
@@ -134,23 +134,23 @@ func (v *VirtualMachineSnapshot) RemoveSnapshotTask(ctx *Context, req *types.Rem
 			Map.Update(vm, changes)
 		})
 
-		Map.Remove(req.This)
+		Map.Remove(ctx, req.This)
 
 		return nil, nil
 	})
 
 	return &methods.RemoveSnapshot_TaskBody{
 		Res: &types.RemoveSnapshot_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (v *VirtualMachineSnapshot) RevertToSnapshotTask(req *types.RevertToSnapshot_Task) soap.HasFault {
+func (v *VirtualMachineSnapshot) RevertToSnapshotTask(ctx *Context, req *types.RevertToSnapshot_Task) soap.HasFault {
 	task := CreateTask(v.Vm, "revertToSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		vm := Map.Get(v.Vm).(*VirtualMachine)
 
-		Map.WithLock(vm, func() {
+		ctx.WithLock(vm, func() {
 			Map.Update(vm, []types.PropertyChange{
 				{Name: "snapshot.currentSnapshot", Val: v.Self},
 			})
@@ -161,7 +161,7 @@ func (v *VirtualMachineSnapshot) RevertToSnapshotTask(req *types.RevertToSnapsho
 
 	return &methods.RevertToSnapshot_TaskBody{
 		Res: &types.RevertToSnapshot_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/storage_resource_manager.go b/simulator/storage_resource_manager.go
index 4d53173c..d169d6d0 100644
--- a/simulator/storage_resource_manager.go
+++ b/simulator/storage_resource_manager.go
@@ -31,7 +31,7 @@ type StorageResourceManager struct {
 	mo.StorageResourceManager
 }
 
-func (m *StorageResourceManager) ConfigureStorageDrsForPodTask(req *types.ConfigureStorageDrsForPod_Task) soap.HasFault {
+func (m *StorageResourceManager) ConfigureStorageDrsForPodTask(ctx *Context, req *types.ConfigureStorageDrsForPod_Task) soap.HasFault {
 	task := CreateTask(m, "configureStorageDrsForPod", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		cluster := Map.Get(req.Pod).(*StoragePod)
 
@@ -51,7 +51,7 @@ func (m *StorageResourceManager) ConfigureStorageDrsForPodTask(req *types.Config
 
 	return &methods.ConfigureStorageDrsForPod_TaskBody{
 		Res: &types.ConfigureStorageDrsForPod_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/task.go b/simulator/task.go
index 3b5939c5..fa969f8c 100644
--- a/simulator/task.go
+++ b/simulator/task.go
@@ -29,9 +29,16 @@ import (
 const vTaskSuffix = "_Task" // vmomi suffix
 const sTaskSuffix = "Task"  // simulator suffix (avoiding golint warning)
 
+// TaskDelay applies to all tasks.
+// Names for DelayConfig.MethodDelay will differ for task and api delays. API
+// level names often look like PowerOff_Task, whereas the task name is simply
+// PowerOff.
+var TaskDelay = DelayConfig{}
+
 type Task struct {
 	mo.Task
 
+	ctx     *Context
 	Execute func(*Task) (types.AnyType, types.BaseMethodFault)
 }
 
@@ -77,33 +84,83 @@ type TaskRunner interface {
 	Run(*Task) (types.AnyType, types.BaseMethodFault)
 }
 
-func (t *Task) Run() types.ManagedObjectReference {
-	now := time.Now()
+// taskReference is a helper struct so we can call AcquireLock in Run()
+type taskReference struct {
+	ref types.ManagedObjectReference
+}
+
+func (tr *taskReference) Reference() types.ManagedObjectReference {
+	return tr.ref
+}
 
-	Map.Update(t, []types.PropertyChange{
-		{Name: "info.startTime", Val: now},
+func (t *Task) Run(ctx *Context) types.ManagedObjectReference {
+	t.ctx = ctx
+	Map.AtomicUpdate(t.ctx, t, []types.PropertyChange{
+		{Name: "info.startTime", Val: time.Now()},
 		{Name: "info.state", Val: types.TaskInfoStateRunning},
 	})
 
-	res, err := t.Execute(t)
-	state := types.TaskInfoStateSuccess
-	var fault interface{}
-	if err != nil {
-		state = types.TaskInfoStateError
-		fault = types.LocalizedMethodFault{
-			Fault:            err,
-			LocalizedMessage: fmt.Sprintf("%T", err),
-		}
+	tr := &taskReference{
+		ref: *t.Info.Entity,
 	}
+	// in most cases, the caller already holds this lock, and we would like
+	// the lock to be held across the "hand off" to the async goroutine.
+	unlock := Map.AcquireLock(ctx, tr)
+
+	go func() {
+		TaskDelay.delay(t.Info.Name)
+		res, err := t.Execute(t)
+		unlock()
+
+		state := types.TaskInfoStateSuccess
+		var fault interface{}
+		if err != nil {
+			state = types.TaskInfoStateError
+			fault = types.LocalizedMethodFault{
+				Fault:            err,
+				LocalizedMessage: fmt.Sprintf("%T", err),
+			}
+		}
 
-	now = time.Now()
-
-	Map.Update(t, []types.PropertyChange{
-		{Name: "info.completeTime", Val: now},
-		{Name: "info.state", Val: state},
-		{Name: "info.result", Val: res},
-		{Name: "info.error", Val: fault},
-	})
+		Map.AtomicUpdate(t.ctx, t, []types.PropertyChange{
+			{Name: "info.completeTime", Val: time.Now()},
+			{Name: "info.state", Val: state},
+			{Name: "info.result", Val: res},
+			{Name: "info.error", Val: fault},
+		})
+	}()
 
 	return t.Self
 }
+
+// RunBlocking() should only be used when an async simulator task needs to wait
+// on another async simulator task.
+// It polls for task completion to avoid the need to set up a PropertyCollector.
+func (t *Task) RunBlocking(ctx *Context) {
+	_ = t.Run(ctx)
+	t.wait()
+}
+
+func (t *Task) wait() {
+	// we do NOT want to share our lock with the tasks's context, because
+	// the goroutine that executes the task will use ctx to update the
+	// state (among other things).
+	isolatedLockingContext := &Context{}
+
+	isRunning := func() bool {
+		var running bool
+		Map.WithLock(isolatedLockingContext, t, func() {
+			switch t.Info.State {
+			case types.TaskInfoStateSuccess, types.TaskInfoStateError:
+				running = false
+			default:
+				running = true
+			}
+		})
+		return running
+	}
+
+	for isRunning() {
+		time.Sleep(10 * time.Millisecond)
+	}
+}
diff --git a/simulator/task_manager.go b/simulator/task_manager.go
index a1b036f4..ae84635e 100644
--- a/simulator/task_manager.go
+++ b/simulator/task_manager.go
@@ -59,6 +59,6 @@ func (m *TaskManager) PutObject(obj mo.Reference) {
 	m.Unlock()
 }
 
-func (*TaskManager) RemoveObject(types.ManagedObjectReference) {}
+func (*TaskManager) RemoveObject(*Context, types.ManagedObjectReference) {}
 
 func (*TaskManager) UpdateObject(mo.Reference, []types.PropertyChange) {}
diff --git a/simulator/view_manager.go b/simulator/view_manager.go
index e9066dea..62653953 100644
--- a/simulator/view_manager.go
+++ b/simulator/view_manager.go
@@ -130,7 +130,7 @@ type ContainerView struct {
 
 func (v *ContainerView) DestroyView(ctx *Context, c *types.DestroyView) soap.HasFault {
 	ctx.Map.RemoveHandler(v)
-	ctx.Session.Remove(c.This)
+	ctx.Session.Remove(ctx, c.This)
 	return destroyView(c.This)
 }
 
@@ -215,8 +215,8 @@ func (v *ContainerView) PutObject(obj mo.Reference) {
 	}
 }
 
-func (v *ContainerView) RemoveObject(obj types.ManagedObjectReference) {
-	Map.RemoveReference(v, &v.View, obj)
+func (v *ContainerView) RemoveObject(ctx *Context, obj types.ManagedObjectReference) {
+	Map.RemoveReference(ctx, v, &v.View, obj)
 }
 
 func (*ContainerView) UpdateObject(mo.Reference, []types.PropertyChange) {}
@@ -259,7 +259,7 @@ func (v *ListView) add(refs []types.ManagedObjectReference) *types.ManagedObject
 }
 
 func (v *ListView) DestroyView(ctx *Context, c *types.DestroyView) soap.HasFault {
-	ctx.Session.Remove(c.This)
+	ctx.Session.Remove(ctx, c.This)
 	return destroyView(c.This)
 }
 
diff --git a/simulator/virtual_disk_manager.go b/simulator/virtual_disk_manager.go
index e597b8b5..f290626d 100644
--- a/simulator/virtual_disk_manager.go
+++ b/simulator/virtual_disk_manager.go
@@ -81,7 +81,7 @@ func vdmCreateVirtualDisk(op types.VirtualDeviceConfigSpecFileOperation, req *ty
 	return nil
 }
 
-func (m *VirtualDiskManager) CreateVirtualDiskTask(_ *Context, req *types.CreateVirtualDisk_Task) soap.HasFault {
+func (m *VirtualDiskManager) CreateVirtualDiskTask(ctx *Context, req *types.CreateVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "createVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		if err := vdmCreateVirtualDisk(types.VirtualDeviceConfigSpecFileOperationCreate, req); err != nil {
 			return "", err
@@ -91,12 +91,12 @@ func (m *VirtualDiskManager) CreateVirtualDiskTask(_ *Context, req *types.Create
 
 	return &methods.CreateVirtualDisk_TaskBody{
 		Res: &types.CreateVirtualDisk_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *VirtualDiskManager) DeleteVirtualDiskTask(_ *Context, req *types.DeleteVirtualDisk_Task) soap.HasFault {
+func (m *VirtualDiskManager) DeleteVirtualDiskTask(ctx *Context, req *types.DeleteVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "deleteVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		fm := Map.FileManager()
 
@@ -116,12 +116,12 @@ func (m *VirtualDiskManager) DeleteVirtualDiskTask(_ *Context, req *types.Delete
 
 	return &methods.DeleteVirtualDisk_TaskBody{
 		Res: &types.DeleteVirtualDisk_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *VirtualDiskManager) MoveVirtualDiskTask(_ *Context, req *types.MoveVirtualDisk_Task) soap.HasFault {
+func (m *VirtualDiskManager) MoveVirtualDiskTask(ctx *Context, req *types.MoveVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "moveVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		fm := Map.FileManager()
 
@@ -146,12 +146,12 @@ func (m *VirtualDiskManager) MoveVirtualDiskTask(_ *Context, req *types.MoveVirt
 
 	return &methods.MoveVirtualDisk_TaskBody{
 		Res: &types.MoveVirtualDisk_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *VirtualDiskManager) CopyVirtualDiskTask(_ *Context, req *types.CopyVirtualDisk_Task) soap.HasFault {
+func (m *VirtualDiskManager) CopyVirtualDiskTask(ctx *Context, req *types.CopyVirtualDisk_Task) soap.HasFault {
 	task := CreateTask(m, "copyVirtualDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		if req.DestSpec != nil {
 			if Map.IsVPX() {
@@ -182,7 +182,7 @@ func (m *VirtualDiskManager) CopyVirtualDiskTask(_ *Context, req *types.CopyVirt
 
 	return &methods.CopyVirtualDisk_TaskBody{
 		Res: &types.CopyVirtualDisk_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/virtual_machine.go b/simulator/virtual_machine.go
index ad30c972..4bd639f2 100644
--- a/simulator/virtual_machine.go
+++ b/simulator/virtual_machine.go
@@ -150,7 +150,7 @@ func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *
 	// Add the default devices
 	defaults.DeviceChange, _ = object.VirtualDeviceList(esx.VirtualDevice).ConfigSpec(types.VirtualDeviceConfigSpecOperationAdd)
 
-	err := vm.configure(&defaults)
+	err := vm.configure(ctx, &defaults)
 	if err != nil {
 		return vm, err
 	}
@@ -169,8 +169,8 @@ func NewVirtualMachine(ctx *Context, parent types.ManagedObjectReference, spec *
 	return vm, nil
 }
 
-func (o *VirtualMachine) RenameTask(r *types.Rename_Task) soap.HasFault {
-	return RenameTask(o, r)
+func (o *VirtualMachine) RenameTask(ctx *Context, r *types.Rename_Task) soap.HasFault {
+	return RenameTask(ctx, o, r)
 }
 
 func (*VirtualMachine) Reload(*types.Reload) soap.HasFault {
@@ -378,7 +378,7 @@ func validateGuestID(id string) types.BaseMethodFault {
 	return &types.InvalidArgument{InvalidProperty: "configSpec.guestId"}
 }
 
-func (vm *VirtualMachine) configure(spec *types.VirtualMachineConfigSpec) types.BaseMethodFault {
+func (vm *VirtualMachine) configure(ctx *Context, spec *types.VirtualMachineConfigSpec) types.BaseMethodFault {
 	vm.apply(spec)
 
 	if spec.MemoryAllocation != nil {
@@ -405,7 +405,7 @@ func (vm *VirtualMachine) configure(spec *types.VirtualMachineConfigSpec) types.
 		}
 	}
 
-	return vm.configureDevices(spec)
+	return vm.configureDevices(ctx, spec)
 }
 
 func getVMFileType(fileName string) types.VirtualMachineFileLayoutExFileType {
@@ -916,7 +916,7 @@ func (vm *VirtualMachine) logPrintf(format string, v ...interface{}) {
 	_ = f.Close()
 }
 
-func (vm *VirtualMachine) create(spec *types.VirtualMachineConfigSpec, register bool) types.BaseMethodFault {
+func (vm *VirtualMachine) create(ctx *Context, spec *types.VirtualMachineConfigSpec, register bool) types.BaseMethodFault {
 	vm.apply(spec)
 
 	if spec.Version != "" {
@@ -951,7 +951,7 @@ func (vm *VirtualMachine) create(spec *types.VirtualMachineConfigSpec, register
 
 	vm.logPrintf("created")
 
-	return vm.configureDevices(spec)
+	return vm.configureDevices(ctx, spec)
 }
 
 var vmwOUI = net.HardwareAddr([]byte{0x0, 0xc, 0x29})
@@ -1042,7 +1042,7 @@ func (vm *VirtualMachine) validateSwitchMembers(id string) types.BaseMethodFault
 	return nil
 }
 
-func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) types.BaseMethodFault {
+func (vm *VirtualMachine) configureDevice(ctx *Context, devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) types.BaseMethodFault {
 	device := spec.Device
 	d := device.GetVirtualDevice()
 	var controller types.BaseVirtualController
@@ -1142,7 +1142,7 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
 			info.Datastore = &ds.Self
 
 			// XXX: compare disk size and free space until windows stat is supported
-			Map.WithLock(ds, func() {
+			ctx.WithLock(ds, func() {
 				ds.Summary.FreeSpace -= getDiskSize(x)
 				ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace
 			})
@@ -1212,7 +1212,7 @@ func (vm *VirtualMachine) configureDevice(devices object.VirtualDeviceList, spec
 	return nil
 }
 
-func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) object.VirtualDeviceList {
+func (vm *VirtualMachine) removeDevice(ctx *Context, devices object.VirtualDeviceList, spec *types.VirtualDeviceConfigSpec) object.VirtualDeviceList {
 	key := spec.Device.GetVirtualDevice().Key
 
 	for i, d := range devices {
@@ -1234,7 +1234,7 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
 					p, _ := parseDatastorePath(file)
 					ds := vm.findDatastore(p.Datastore)
 
-					Map.WithLock(ds, func() {
+					ctx.WithLock(ds, func() {
 						ds.Summary.FreeSpace += getDiskSize(device)
 						ds.Info.GetDatastoreInfo().FreeSpace = ds.Summary.FreeSpace
 					})
@@ -1246,10 +1246,12 @@ func (vm *VirtualMachine) removeDevice(devices object.VirtualDeviceList, spec *t
 					if dc == nil {
 						continue // parent was destroyed
 					}
-					dm.DeleteVirtualDiskTask(internalContext, &types.DeleteVirtualDisk_Task{
+					res := dm.DeleteVirtualDiskTask(ctx, &types.DeleteVirtualDisk_Task{
 						Name:       file,
 						Datacenter: &dc.Self,
 					})
+					ctask := Map.Get(res.(*methods.DeleteVirtualDisk_TaskBody).Res.Returnval).(*Task)
+					ctask.wait()
 				}
 			}
 			Map.Update(vm, []types.PropertyChange{
@@ -1319,7 +1321,7 @@ func (vm *VirtualMachine) genVmdkPath(p object.DatastorePath) (string, types.Bas
 	}
 }
 
-func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec) types.BaseMethodFault {
+func (vm *VirtualMachine) configureDevices(ctx *Context, spec *types.VirtualMachineConfigSpec) types.BaseMethodFault {
 	devices := object.VirtualDeviceList(vm.Config.Hardware.Device)
 
 	for i, change := range spec.DeviceChange {
@@ -1358,7 +1360,7 @@ func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec)
 			}
 
 			key := device.Key
-			err := vm.configureDevice(devices, dspec)
+			err := vm.configureDevice(ctx, devices, dspec)
 			if err != nil {
 				return err
 			}
@@ -1379,17 +1381,17 @@ func (vm *VirtualMachine) configureDevices(spec *types.VirtualMachineConfigSpec)
 			if rspec.Device == nil {
 				return invalid
 			}
-			devices = vm.removeDevice(devices, &rspec)
+			devices = vm.removeDevice(ctx, devices, &rspec)
 			device.DeviceInfo.GetDescription().Summary = "" // regenerate summary
 
-			err := vm.configureDevice(devices, dspec)
+			err := vm.configureDevice(ctx, devices, dspec)
 			if err != nil {
 				return err
 			}
 
 			devices = append(devices, dspec.Device)
 		case types.VirtualDeviceConfigSpecOperationRemove:
-			devices = vm.removeDevice(devices, dspec)
+			devices = vm.removeDevice(ctx, devices, dspec)
 		}
 	}
 
@@ -1478,7 +1480,7 @@ func (vm *VirtualMachine) PowerOnVMTask(ctx *Context, c *types.PowerOnVM_Task) s
 
 	return &methods.PowerOnVM_TaskBody{
 		Res: &types.PowerOnVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1489,7 +1491,7 @@ func (vm *VirtualMachine) PowerOffVMTask(ctx *Context, c *types.PowerOffVM_Task)
 
 	return &methods.PowerOffVM_TaskBody{
 		Res: &types.PowerOffVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1500,7 +1502,7 @@ func (vm *VirtualMachine) SuspendVMTask(ctx *Context, req *types.SuspendVM_Task)
 
 	return &methods.SuspendVM_TaskBody{
 		Res: &types.SuspendVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1509,18 +1511,21 @@ func (vm *VirtualMachine) ResetVMTask(ctx *Context, req *types.ResetVM_Task) soa
 	task := CreateTask(vm, "reset", func(task *Task) (types.AnyType, types.BaseMethodFault) {
 		res := vm.PowerOffVMTask(ctx, &types.PowerOffVM_Task{This: vm.Self})
 		ctask := Map.Get(res.(*methods.PowerOffVM_TaskBody).Res.Returnval).(*Task)
+		ctask.wait()
 		if ctask.Info.Error != nil {
 			return nil, ctask.Info.Error.Fault
 		}
 
-		_ = vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{This: vm.Self})
+		res = vm.PowerOnVMTask(ctx, &types.PowerOnVM_Task{This: vm.Self})
+		ctask = Map.Get(res.(*methods.PowerOnVM_TaskBody).Res.Returnval).(*Task)
+		ctask.wait()
 
 		return nil, nil
 	})
 
 	return &methods.ResetVM_TaskBody{
 		Res: &types.ResetVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1559,19 +1564,19 @@ func (vm *VirtualMachine) ReconfigVMTask(ctx *Context, req *types.ReconfigVM_Tas
 			}
 		}
 
-		err := vm.configure(&req.Spec)
+		err := vm.configure(ctx, &req.Spec)
 
 		return nil, err
 	})
 
 	return &methods.ReconfigVM_TaskBody{
 		Res: &types.ReconfigVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (vm *VirtualMachine) UpgradeVMTask(req *types.UpgradeVM_Task) soap.HasFault {
+func (vm *VirtualMachine) UpgradeVMTask(ctx *Context, req *types.UpgradeVM_Task) soap.HasFault {
 	body := &methods.UpgradeVM_TaskBody{}
 
 	task := CreateTask(vm, "upgradeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
@@ -1584,7 +1589,7 @@ func (vm *VirtualMachine) UpgradeVMTask(req *types.UpgradeVM_Task) soap.HasFault
 	})
 
 	body.Res = &types.UpgradeVM_TaskResponse{
-		Returnval: task.Run(),
+		Returnval: task.Run(ctx),
 	}
 
 	return body
@@ -1609,12 +1614,12 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
 		// Remove all devices
 		devices := object.VirtualDeviceList(vm.Config.Hardware.Device)
 		spec, _ := devices.ConfigSpec(types.VirtualDeviceConfigSpecOperationRemove)
-		vm.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: spec})
+		vm.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: spec})
 
 		// Delete VM files from the datastore (ignoring result for now)
 		m := Map.FileManager()
 
-		_ = m.DeleteDatastoreFileTask(&types.DeleteDatastoreFile_Task{
+		_ = m.DeleteDatastoreFileTask(ctx, &types.DeleteDatastoreFile_Task{
 			This:       m.Reference(),
 			Name:       vm.Config.Files.LogDirectory,
 			Datacenter: &dc.Self,
@@ -1627,7 +1632,7 @@ func (vm *VirtualMachine) DestroyTask(ctx *Context, req *types.Destroy_Task) soa
 
 	return &methods.Destroy_TaskBody{
 		Res: &types.Destroy_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1649,20 +1654,20 @@ func (vm *VirtualMachine) UnregisterVM(ctx *Context, c *types.UnregisterVM) soap
 	}
 
 	host := Map.Get(*vm.Runtime.Host).(*HostSystem)
-	Map.RemoveReference(host, &host.Vm, vm.Self)
+	Map.RemoveReference(ctx, host, &host.Vm, vm.Self)
 
 	if vm.ResourcePool != nil {
 		switch pool := Map.Get(*vm.ResourcePool).(type) {
 		case *ResourcePool:
-			Map.RemoveReference(pool, &pool.Vm, vm.Self)
+			Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
 		case *VirtualApp:
-			Map.RemoveReference(pool, &pool.Vm, vm.Self)
+			Map.RemoveReference(ctx, pool, &pool.Vm, vm.Self)
 		}
 	}
 
 	for i := range vm.Datastore {
 		ds := Map.Get(vm.Datastore[i]).(*Datastore)
-		Map.RemoveReference(ds, &ds.Vm, vm.Self)
+		Map.RemoveReference(ctx, ds, &ds.Vm, vm.Self)
 	}
 
 	ctx.postEvent(&types.VmRemovedEvent{VmEvent: vm.event()})
@@ -1768,11 +1773,13 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 			return nil, ctask.Info.Error.Fault
 		}
 
+		ctask.wait()
+
 		ref := ctask.Info.Result.(types.ManagedObjectReference)
 		clone := Map.Get(ref).(*VirtualMachine)
-		clone.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange})
+		clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Location.DeviceChange})
 		if req.Spec.Config != nil && req.Spec.Config.DeviceChange != nil {
-			clone.configureDevices(&types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Config.DeviceChange})
+			clone.configureDevices(ctx, &types.VirtualMachineConfigSpec{DeviceChange: req.Spec.Config.DeviceChange})
 		}
 
 		if req.Spec.Template {
@@ -1789,7 +1796,7 @@ func (vm *VirtualMachine) CloneVMTask(ctx *Context, req *types.CloneVM_Task) soa
 
 	return &methods.CloneVM_TaskBody{
 		Res: &types.CloneVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1800,7 +1807,7 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 
 		if ref := req.Spec.Datastore; ref != nil {
 			ds := Map.Get(*ref).(*Datastore)
-			Map.RemoveReference(ds, &ds.Vm, *ref)
+			Map.RemoveReference(ctx, ds, &ds.Vm, *ref)
 
 			// TODO: migrate vm.Config.Files, vm.Summary.Config.VmPathName, vm.Layout and vm.LayoutEx
 
@@ -1809,14 +1816,14 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 
 		if ref := req.Spec.Pool; ref != nil {
 			pool := Map.Get(*ref).(*ResourcePool)
-			Map.RemoveReference(pool, &pool.Vm, *ref)
+			Map.RemoveReference(ctx, pool, &pool.Vm, *ref)
 
 			changes = append(changes, types.PropertyChange{Name: "resourcePool", Val: ref})
 		}
 
 		if ref := req.Spec.Host; ref != nil {
 			host := Map.Get(*ref).(*HostSystem)
-			Map.RemoveReference(host, &host.Vm, *ref)
+			Map.RemoveReference(ctx, host, &host.Vm, *ref)
 
 			changes = append(changes,
 				types.PropertyChange{Name: "runtime.host", Val: ref},
@@ -1838,7 +1845,7 @@ func (vm *VirtualMachine) RelocateVMTask(ctx *Context, req *types.RelocateVM_Tas
 
 	return &methods.RelocateVM_TaskBody{
 		Res: &types.RelocateVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -1919,7 +1926,7 @@ func (vm *VirtualMachine) customize(ctx *Context) {
 	ctx.postEvent(&types.CustomizationSucceeded{CustomizationEvent: event})
 }
 
-func (vm *VirtualMachine) CustomizeVMTask(req *types.CustomizeVM_Task) soap.HasFault {
+func (vm *VirtualMachine) CustomizeVMTask(ctx *Context, req *types.CustomizeVM_Task) soap.HasFault {
 	task := CreateTask(vm, "customizeVm", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		if vm.Runtime.PowerState == types.VirtualMachinePowerStatePoweredOn {
 			return nil, &types.InvalidPowerState{
@@ -1945,12 +1952,12 @@ func (vm *VirtualMachine) CustomizeVMTask(req *types.CustomizeVM_Task) soap.HasF
 
 	return &methods.CustomizeVM_TaskBody{
 		Res: &types.CustomizeVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soap.HasFault {
+func (vm *VirtualMachine) CreateSnapshotTask(ctx *Context, req *types.CreateSnapshot_Task) soap.HasFault {
 	task := CreateTask(vm, "createSnapshot", func(t *Task) (types.AnyType, types.BaseMethodFault) {
 		var changes []types.PropertyChange
 
@@ -2001,12 +2008,12 @@ func (vm *VirtualMachine) CreateSnapshotTask(req *types.CreateSnapshot_Task) soa
 
 	return &methods.CreateSnapshot_TaskBody{
 		Res: &types.CreateSnapshot_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (vm *VirtualMachine) RevertToCurrentSnapshotTask(req *types.RevertToCurrentSnapshot_Task) soap.HasFault {
+func (vm *VirtualMachine) RevertToCurrentSnapshotTask(ctx *Context, req *types.RevertToCurrentSnapshot_Task) soap.HasFault {
 	body := &methods.RevertToCurrentSnapshot_TaskBody{}
 
 	if vm.Snapshot == nil || vm.Snapshot.CurrentSnapshot == nil {
@@ -2020,7 +2027,7 @@ func (vm *VirtualMachine) RevertToCurrentSnapshotTask(req *types.RevertToCurrent
 	})
 
 	body.Res = &types.RevertToCurrentSnapshot_TaskResponse{
-		Returnval: task.Run(),
+		Returnval: task.Run(ctx),
 	}
 
 	return body
@@ -2040,7 +2047,7 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.Remove
 
 		for _, ref := range refs {
 			Map.Get(ref).(*VirtualMachineSnapshot).removeSnapshotFiles(ctx)
-			Map.Remove(ref)
+			Map.Remove(ctx, ref)
 		}
 
 		return nil, nil
@@ -2048,7 +2055,7 @@ func (vm *VirtualMachine) RemoveAllSnapshotsTask(ctx *Context, req *types.Remove
 
 	return &methods.RemoveAllSnapshots_TaskBody{
 		Res: &types.RemoveAllSnapshots_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/vstorage_object_manager.go b/simulator/vstorage_object_manager.go
index 3c4107f0..361aa842 100644
--- a/simulator/vstorage_object_manager.go
+++ b/simulator/vstorage_object_manager.go
@@ -128,7 +128,7 @@ func (m *VcenterVStorageObjectManager) ReconcileDatastoreInventoryTask(ctx *Cont
 
 	return &methods.ReconcileDatastoreInventory_TaskBody{
 		Res: &types.ReconcileDatastoreInventory_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -267,19 +267,19 @@ func (m *VcenterVStorageObjectManager) createObject(req *types.CreateDisk_Task,
 
 }
 
-func (m *VcenterVStorageObjectManager) CreateDiskTask(req *types.CreateDisk_Task) soap.HasFault {
+func (m *VcenterVStorageObjectManager) CreateDiskTask(ctx *Context, req *types.CreateDisk_Task) soap.HasFault {
 	task := CreateTask(m, "createDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		return m.createObject(req, false)
 	})
 
 	return &methods.CreateDisk_TaskBody{
 		Res: &types.CreateDisk_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(req *types.DeleteVStorageObject_Task) soap.HasFault {
+func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(ctx *Context, req *types.DeleteVStorageObject_Task) soap.HasFault {
 	task := CreateTask(m, "deleteDisk", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		obj := m.object(req.Datastore, req.Id)
 		if obj == nil {
@@ -290,7 +290,7 @@ func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(req *types.Delet
 		ds := Map.Get(req.Datastore).(*Datastore)
 		dc := Map.getEntityDatacenter(ds)
 		dm := Map.VirtualDiskManager()
-		dm.DeleteVirtualDiskTask(internalContext, &types.DeleteVirtualDisk_Task{
+		dm.DeleteVirtualDiskTask(ctx, &types.DeleteVirtualDisk_Task{
 			Name:       backing.FilePath,
 			Datacenter: &dc.Self,
 		})
@@ -302,7 +302,7 @@ func (m *VcenterVStorageObjectManager) DeleteVStorageObjectTask(req *types.Delet
 
 	return &methods.DeleteVStorageObject_TaskBody{
 		Res: &types.DeleteVStorageObject_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
@@ -322,7 +322,7 @@ func (m *VcenterVStorageObjectManager) RetrieveSnapshotInfo(req *types.RetrieveS
 	return body
 }
 
-func (m *VcenterVStorageObjectManager) VStorageObjectCreateSnapshotTask(req *types.VStorageObjectCreateSnapshot_Task) soap.HasFault {
+func (m *VcenterVStorageObjectManager) VStorageObjectCreateSnapshotTask(ctx *Context, req *types.VStorageObjectCreateSnapshot_Task) soap.HasFault {
 	task := CreateTask(m, "createSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		obj := m.object(req.Datastore, req.Id)
 		if obj == nil {
@@ -344,12 +344,12 @@ func (m *VcenterVStorageObjectManager) VStorageObjectCreateSnapshotTask(req *typ
 
 	return &methods.VStorageObjectCreateSnapshot_TaskBody{
 		Res: &types.VStorageObjectCreateSnapshot_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
 
-func (m *VcenterVStorageObjectManager) DeleteSnapshotTask(req *types.DeleteSnapshot_Task) soap.HasFault {
+func (m *VcenterVStorageObjectManager) DeleteSnapshotTask(ctx *Context, req *types.DeleteSnapshot_Task) soap.HasFault {
 	task := CreateTask(m, "deleteSnapshot", func(*Task) (types.AnyType, types.BaseMethodFault) {
 		obj := m.object(req.Datastore, req.Id)
 		if obj != nil {
@@ -365,7 +365,7 @@ func (m *VcenterVStorageObjectManager) DeleteSnapshotTask(req *types.DeleteSnaps
 
 	return &methods.DeleteSnapshot_TaskBody{
 		Res: &types.DeleteSnapshot_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/vsan/simulator/simulator.go b/vsan/simulator/simulator.go
index d684f427..a34df98c 100644
--- a/vsan/simulator/simulator.go
+++ b/vsan/simulator/simulator.go
@@ -57,7 +57,7 @@ func (s *StretchedClusterSystem) VSANVcConvertToStretchedCluster(ctx *simulator.
 
 	return &methods.VSANVcConvertToStretchedClusterBody{
 		Res: &types.VSANVcConvertToStretchedClusterResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }


Test patch:
diff --git a/lookup/client_test.go b/lookup/client_test.go
index 31d681ef..d7e9d0df 100644
--- a/lookup/client_test.go
+++ b/lookup/client_test.go
@@ -30,7 +30,7 @@ import (
 func TestClient(t *testing.T) {
 	// lookup/simulator/simulator_test.go has the functional test..
 	// in this test we just verify requests to /lookup/sdk return 404
-	s := simulator.New(simulator.NewServiceInstance(vpx.ServiceContent, vpx.RootFolder))
+	s := simulator.New(simulator.NewServiceInstance(simulator.SpoofContext(), vpx.ServiceContent, vpx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/object/vm_guest_test.go b/object/vm_guest_test.go
index b83380a8..428c1333 100644
--- a/object/vm_guest_test.go
+++ b/object/vm_guest_test.go
@@ -61,7 +61,7 @@ func TestVirtualMachineWaitForIP(t *testing.T) {
 		wg.Wait()
 
 		wg.Add(1)
-		simulator.Map.WithLock(obj.Reference(), func() {
+		simulator.Map.WithLock(simulator.SpoofContext(), obj.Reference(), func() {
 			simulator.Map.Update(obj, []types.PropertyChange{
 				{Name: "guest.ipAddress", Val: "10.0.0.1"},
 			})
diff --git a/session/keepalive/handler_test.go b/session/keepalive/handler_test.go
index 61123674..3734d15a 100644
--- a/session/keepalive/handler_test.go
+++ b/session/keepalive/handler_test.go
@@ -121,7 +121,6 @@ func TestHandlerREST(t *testing.T) {
 
 		rc := rest.NewClient(vc)
 		rc.Transport = keepalive.NewHandlerREST(rc, time.Millisecond, i.Send)
-		err = rc.Login(ctx, simulator.DefaultLogin)
 		if err != nil {
 			t.Fatal(err)
 		}
diff --git a/simulator/authorization_manager_test.go b/simulator/authorization_manager_test.go
index d32eafae..a92e4e61 100644
--- a/simulator/authorization_manager_test.go
+++ b/simulator/authorization_manager_test.go
@@ -27,7 +27,7 @@ func TestAuthorizationManager(t *testing.T) {
 	for i := 0; i < 2; i++ {
 		model := VPX()
 
-		_ = New(NewServiceInstance(model.ServiceContent, model.RootFolder)) // 2nd pass panics w/o copying RoleList
+		_ = New(NewServiceInstance(SpoofContext(), model.ServiceContent, model.RootFolder)) // 2nd pass panics w/o copying RoleList
 
 		authz := Map.Get(*vpx.ServiceContent.AuthorizationManager).(*AuthorizationManager)
 		authz.RemoveAuthorizationRole(&types.RemoveAuthorizationRole{
diff --git a/simulator/cluster_compute_resource_test.go b/simulator/cluster_compute_resource_test.go
index c6791a81..fc16be4b 100644
--- a/simulator/cluster_compute_resource_test.go
+++ b/simulator/cluster_compute_resource_test.go
@@ -29,7 +29,7 @@ import (
 
 func TestClusterESX(t *testing.T) {
 	content := esx.ServiceContent
-	s := New(NewServiceInstance(content, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), content, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -55,7 +55,7 @@ func TestClusterESX(t *testing.T) {
 
 func TestClusterVC(t *testing.T) {
 	content := vpx.ServiceContent
-	s := New(NewServiceInstance(content, vpx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), content, vpx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/simulator/entity_test.go b/simulator/entity_test.go
index 0e9d391a..2fb75332 100644
--- a/simulator/entity_test.go
+++ b/simulator/entity_test.go
@@ -43,7 +43,7 @@ func TestRename(t *testing.T) {
 
 	f1 := Map.Get(vmFolder.ChildEntity[0]).(*Folder) // "F1"
 
-	id := vmFolder.CreateFolder(internalContext, &types.CreateFolder{
+	id := vmFolder.CreateFolder(SpoofContext(), &types.CreateFolder{
 		This: vmFolder.Reference(),
 		Name: "F2",
 	}).(*methods.CreateFolderBody).Res.Returnval
@@ -54,12 +54,13 @@ func TestRename(t *testing.T) {
 	name := f1.Name
 
 	for _, expect := range states {
-		id = f2.RenameTask(&types.Rename_Task{
+		id = f2.RenameTask(SpoofContext(), &types.Rename_Task{
 			This:    f2.Reference(),
 			NewName: name,
 		}).(*methods.Rename_TaskBody).Res.Returnval
 
 		task := Map.Get(id).(*Task)
+		task.wait()
 
 		if task.Info.State != expect {
 			t.Errorf("state=%s", task.Info.State)
diff --git a/simulator/example_extend_test.go b/simulator/example_extend_test.go
index 861c9e16..016de95e 100644
--- a/simulator/example_extend_test.go
+++ b/simulator/example_extend_test.go
@@ -35,14 +35,14 @@ type BusyVM struct {
 }
 
 // Override simulator.VirtualMachine.PowerOffVMTask to inject faults
-func (vm *BusyVM) PowerOffVMTask(req *types.PowerOffVM_Task) soap.HasFault {
+func (vm *BusyVM) PowerOffVMTask(ctx *simulator.Context, req *types.PowerOffVM_Task) soap.HasFault {
 	task := simulator.CreateTask(req.This, "powerOff", func(*simulator.Task) (types.AnyType, types.BaseMethodFault) {
 		return nil, &types.TaskInProgress{}
 	})
 
 	return &methods.PowerOffVM_TaskBody{
 		Res: &types.PowerOffVM_TaskResponse{
-			Returnval: task.Run(),
+			Returnval: task.Run(ctx),
 		},
 	}
 }
diff --git a/simulator/example_test.go b/simulator/example_test.go
index 97a341d0..bb340728 100644
--- a/simulator/example_test.go
+++ b/simulator/example_test.go
@@ -151,8 +151,8 @@ func ExampleFolder_AddOpaqueNetwork() {
 			OpaqueNetworkType: "nsx.LogicalSwitch",
 		}
 
-		// Add NSX backed OpaqueNetwork, calling the simulator.Folder method directly
-		err = folder.AddOpaqueNetwork(spec)
+		// Add NSX backed OpaqueNetwork, calling the simulator.Folder method directly.
+		err = folder.AddOpaqueNetwork(simulator.SpoofContext(), spec)
 		if err != nil {
 			return err
 		}
diff --git a/simulator/folder_test.go b/simulator/folder_test.go
index 56a3a859..a84c7c64 100644
--- a/simulator/folder_test.go
+++ b/simulator/folder_test.go
@@ -51,7 +51,7 @@ func addStandaloneHostTask(folder *object.Folder, spec types.HostConnectSpec) (*
 
 func TestFolderESX(t *testing.T) {
 	content := esx.ServiceContent
-	s := New(NewServiceInstance(content, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), content, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -99,7 +99,7 @@ func TestFolderESX(t *testing.T) {
 
 func TestFolderVC(t *testing.T) {
 	content := vpx.ServiceContent
-	s := New(NewServiceInstance(content, vpx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), content, vpx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -297,7 +297,7 @@ func TestRegisterVm(t *testing.T) {
 				new(types.NotFound), func() { req.Path = vm.Config.Files.VmPathName },
 			},
 			{
-				new(types.AlreadyExists), func() { Map.Remove(vm.Reference()) },
+				new(types.AlreadyExists), func() { Map.Remove(SpoofContext(), vm.Reference()) },
 			},
 			{
 				nil, func() {},
@@ -310,6 +310,9 @@ func TestRegisterVm(t *testing.T) {
 				t.Fatal(err)
 			}
 
+			ct := object.NewTask(c.Client, res.Returnval)
+			_ = ct.Wait(ctx)
+
 			rt := Map.Get(res.Returnval).(*Task)
 
 			if step.e != nil {
@@ -335,14 +338,15 @@ func TestRegisterVm(t *testing.T) {
 			t.Error("expected new moref")
 		}
 
-		_, _ = nvm.PowerOn(ctx)
+		onTask, _ := nvm.PowerOn(ctx)
+		_ = onTask.Wait(ctx)
 
 		steps = []struct {
 			e interface{}
 			f func()
 		}{
 			{
-				types.InvalidPowerState{}, func() { _, _ = nvm.PowerOff(ctx) },
+				types.InvalidPowerState{}, func() { offTask, _ := nvm.PowerOff(ctx); _ = offTask.Wait(ctx) },
 			},
 			{
 				nil, func() {},
diff --git a/simulator/host_datastore_system_test.go b/simulator/host_datastore_system_test.go
index ce55a348..7e53de78 100644
--- a/simulator/host_datastore_system_test.go
+++ b/simulator/host_datastore_system_test.go
@@ -29,7 +29,7 @@ import (
 )
 
 func TestHostDatastoreSystem(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/simulator/host_network_system_test.go b/simulator/host_network_system_test.go
index 17c81b98..d3976ac9 100644
--- a/simulator/host_network_system_test.go
+++ b/simulator/host_network_system_test.go
@@ -30,7 +30,7 @@ import (
 func TestHostNetworkSystem(t *testing.T) {
 	ctx := context.Background()
 
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	host := object.NewHostSystem(s.client, esx.HostSystem.Reference())
 
diff --git a/simulator/host_system_test.go b/simulator/host_system_test.go
index a8f85a6a..82c49024 100644
--- a/simulator/host_system_test.go
+++ b/simulator/host_system_test.go
@@ -27,7 +27,7 @@ import (
 )
 
 func TestDefaultESX(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/simulator/property_collector_test.go b/simulator/property_collector_test.go
index dbfb596c..22e9f682 100644
--- a/simulator/property_collector_test.go
+++ b/simulator/property_collector_test.go
@@ -48,7 +48,7 @@ func TestRetrieveProperties(t *testing.T) {
 	}
 
 	for _, config := range configs {
-		s := New(NewServiceInstance(config.content, config.folder))
+		s := New(NewServiceInstance(SpoofContext(), config.content, config.folder))
 
 		ts := s.NewServer()
 		defer ts.Close()
@@ -232,7 +232,7 @@ func TestRetrieveProperties(t *testing.T) {
 		}
 
 		// Expect ManagedObjectNotFoundError
-		Map.Remove(dc.Reference())
+		Map.Remove(SpoofContext(), dc.Reference())
 		err = client.RetrieveOne(ctx, dc.Reference(), []string{"name"}, &mdc)
 		if err == nil {
 			t.Fatal("expected error")
@@ -242,7 +242,7 @@ func TestRetrieveProperties(t *testing.T) {
 
 func TestWaitForUpdates(t *testing.T) {
 	folder := esx.RootFolder
-	s := New(NewServiceInstance(esx.ServiceContent, folder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, folder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -299,7 +299,7 @@ func TestWaitForUpdates(t *testing.T) {
 	wg.Wait()
 
 	// test object not found
-	Map.Remove(folder.Reference())
+	Map.Remove(SpoofContext(), folder.Reference())
 
 	err = property.Wait(ctx, pc, folder.Reference(), props, cb(true))
 	if err == nil {
@@ -446,9 +446,11 @@ func TestIncrementalWaitForUpdates(t *testing.T) {
 
 	wg.Wait() // wait for 1st enter
 	wg.Add(1)
-	_, _ = vm.PowerOff(ctx)
-	_, _ = vm.Destroy(ctx)
+	task, _ := vm.PowerOff(ctx)
+	_ = task.Wait(ctx)
+	task, _ = vm.Destroy(ctx)
 	wg.Wait() // wait for Delete to be reported
+	task.Wait(ctx)
 }
 
 func TestWaitForUpdatesOneUpdateCalculation(t *testing.T) {
@@ -705,7 +707,7 @@ func TestExtractEmbeddedField(t *testing.T) {
 
 	Map.Put(x)
 
-	obj, ok := getObject(internalContext, x.Reference())
+	obj, ok := getObject(SpoofContext(), x.Reference())
 	if !ok {
 		t.Error("expected obj")
 	}
@@ -801,7 +803,7 @@ func TestPropertyCollectorFold(t *testing.T) {
 
 func TestPropertyCollectorInvalidSpecName(t *testing.T) {
 	obj := Map.Put(new(Folder))
-	folderPutChild(internalContext, &obj.(*Folder).Folder, new(Folder))
+	folderPutChild(SpoofContext(), &obj.(*Folder).Folder, new(Folder))
 
 	pc := &PropertyCollector{}
 
@@ -834,7 +836,7 @@ func TestPropertyCollectorInvalidSpecName(t *testing.T) {
 		},
 	}
 
-	_, err := pc.collect(internalContext, &req)
+	_, err := pc.collect(SpoofContext(), &req)
 	if err == nil {
 		t.Fatal("expected error")
 	}
diff --git a/simulator/registry_test.go b/simulator/registry_test.go
index d9333b82..6fb5c8c0 100644
--- a/simulator/registry_test.go
+++ b/simulator/registry_test.go
@@ -38,7 +38,7 @@ func TestRegistry(t *testing.T) {
 		t.Fail()
 	}
 
-	r.Remove(ref)
+	r.Remove(SpoofContext(), ref)
 
 	if r.Get(ref) != nil {
 		t.Fail()
diff --git a/simulator/session_manager_test.go b/simulator/session_manager_test.go
index 20e9d58f..4057dd2a 100644
--- a/simulator/session_manager_test.go
+++ b/simulator/session_manager_test.go
@@ -236,7 +236,7 @@ func TestSessionManagerAuth(t *testing.T) {
 func TestSessionManagerLoginExtension(t *testing.T) {
 	ctx := context.Background()
 
-	s := New(NewServiceInstance(vpx.ServiceContent, vpx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), vpx.ServiceContent, vpx.RootFolder))
 	s.TLS = new(tls.Config)
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/simulator/simulator_test.go b/simulator/simulator_test.go
index 433be1b4..5781c791 100644
--- a/simulator/simulator_test.go
+++ b/simulator/simulator_test.go
@@ -214,7 +214,7 @@ func TestServeHTTP(t *testing.T) {
 	}
 
 	for _, config := range configs {
-		s := New(NewServiceInstance(config.content, config.folder))
+		s := New(NewServiceInstance(SpoofContext(), config.content, config.folder))
 		ts := s.NewServer()
 		defer ts.Close()
 
@@ -302,7 +302,7 @@ func TestServeAbout(t *testing.T) {
 }
 
 func TestServeHTTPS(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 	s.TLS = new(tls.Config)
 	ts := s.NewServer()
 	defer ts.Close()
@@ -396,7 +396,7 @@ type errorNoSuchMethod struct {
 }
 
 func TestServeHTTPErrors(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -429,7 +429,7 @@ func TestServeHTTPErrors(t *testing.T) {
 	}
 
 	// cover the no such object path
-	Map.Remove(vim25.ServiceInstance)
+	Map.Remove(SpoofContext(), vim25.ServiceInstance)
 	_, err = methods.GetCurrentTime(ctx, client)
 	if err == nil {
 		t.Error("expected error")
@@ -468,3 +468,74 @@ func TestServeHTTPErrors(t *testing.T) {
 		t.Errorf("expected status %d, got %s", http.StatusBadRequest, res.Status)
 	}
 }
+
+func TestDelay(t *testing.T) {
+	m := ESX()
+	defer m.Remove()
+
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := m.Service.NewServer()
+	defer s.Close()
+
+	client, err := govmomi.NewClient(context.Background(), s.URL, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	simvm := Map.Any("VirtualMachine").(*VirtualMachine)
+	vm := object.NewVirtualMachine(client.Client, simvm.Reference())
+
+	m.Service.delay.Delay = 1000
+
+	ctx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
+	defer cancel()
+
+	_, err = vm.PowerOff(ctx)
+	if err == nil {
+		t.Fatalf("expected timeout initiating task")
+	}
+	// give time for task to finish
+	time.Sleep(1000 * time.Millisecond)
+}
+
+func TestDelayTask(t *testing.T) {
+	m := ESX()
+	defer m.Remove()
+
+	err := m.Create()
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	s := m.Service.NewServer()
+	defer s.Close()
+
+	client, err := govmomi.NewClient(context.Background(), s.URL, true)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	simvm := Map.Any("VirtualMachine").(*VirtualMachine)
+	vm := object.NewVirtualMachine(client.Client, simvm.Reference())
+
+	TaskDelay.Delay = 1000
+	defer func() { TaskDelay.Delay = 0 }()
+
+	task, err := vm.PowerOff(context.Background())
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	timeoutCtx, cancel := context.WithTimeout(context.Background(), 20*time.Millisecond)
+	defer cancel()
+	err = task.Wait(timeoutCtx)
+	if err == nil {
+		t.Fatal("expected timeout waiting for task")
+	}
+	// make sure to wait for task, or else it can run while other tests run!
+	task.Wait(context.Background())
+}
diff --git a/simulator/task_test.go b/simulator/task_test.go
index de1d3185..502b2ff4 100644
--- a/simulator/task_test.go
+++ b/simulator/task_test.go
@@ -49,7 +49,7 @@ func TestNewTask(t *testing.T) {
 		t.Errorf("descriptionId=%s", info.DescriptionId)
 	}
 
-	task.Run()
+	task.RunBlocking(SpoofContext())
 
 	if info.State != types.TaskInfoStateSuccess {
 		t.Fail()
@@ -57,7 +57,8 @@ func TestNewTask(t *testing.T) {
 
 	add.fault = &types.ManagedObjectNotFound{}
 
-	task.Run()
+	task.Run(SpoofContext())
+	task.wait()
 
 	if info.State != types.TaskInfoStateError {
 		t.Fail()
diff --git a/simulator/user_directory_test.go b/simulator/user_directory_test.go
index 6dde0c58..1cab68df 100644
--- a/simulator/user_directory_test.go
+++ b/simulator/user_directory_test.go
@@ -27,7 +27,7 @@ import (
 )
 
 func TestUserDirectory(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
@@ -92,7 +92,7 @@ func TestUserDirectory(t *testing.T) {
 }
 
 func TestUserDirectoryExactlyMatch(t *testing.T) {
-	s := New(NewServiceInstance(esx.ServiceContent, esx.RootFolder))
+	s := New(NewServiceInstance(SpoofContext(), esx.ServiceContent, esx.RootFolder))
 
 	ts := s.NewServer()
 	defer ts.Close()
diff --git a/simulator/virtual_machine_test.go b/simulator/virtual_machine_test.go
index bb79f6d9..2055c912 100644
--- a/simulator/virtual_machine_test.go
+++ b/simulator/virtual_machine_test.go
@@ -1144,8 +1144,14 @@ func TestVmRefreshStorageInfo(t *testing.T) {
 		t.Errorf("expected %d, got %d", fileLayoutExCount+1, len(vmm.LayoutEx.File))
 	}
 
-	_ = f.Close()
-	_ = os.Remove(f.Name())
+	err = f.Close()
+	if err != nil {
+		t.Fatalf("f.Close failure: %v", err)
+	}
+	err = os.Remove(f.Name())
+	if err != nil {
+		t.Fatalf("os.Remove(%s) failure: %v", f.Name(), err)
+	}
 
 	if err = vm.RefreshStorageInfo(ctx); err != nil {
 		t.Error(err)