Class: Roby::Transaction

Inherits:
Plan show all
Includes:
Log::TransactionHooks
Defined in:
lib/roby/transactions.rb,
lib/roby/query.rb,
lib/roby/transactions/updates.rb

Overview

A transaction is a special kind of plan. It allows to build plans in a separate sandbox, and then to apply the modifications to the real plan (using #commit_transaction), or to discard all modifications (using #discard)

Direct Known Subclasses

Distributed::Transaction

Defined Under Namespace

Modules: Proxy

Constant Summary

Constants included from Log::TransactionHooks

Log::TransactionHooks::HOOKS

Constants included from Log::PlanHooks

Log::PlanHooks::HOOKS

Constants included from Log::BasicObjectHooks

Log::BasicObjectHooks::HOOKS

Instance Attribute Summary collapse

Attributes inherited from Plan

#force_gc, #free_events, #gc_quarantine, #keepalive, #known_tasks, #missions, #repairs, #task_events, #task_index, #transactions

Attributes inherited from BasicObject

#distribute

Instance Method Summary collapse

Methods inherited from Plan

#add_repair, #added_transaction, can_gc?, #discarded, #discover_event_set, #discover_task_set, #discovered, #discovered_events, #discovered_tasks, #droby_dump, #each_task, #empty?, #finalized, #finalized_event, #finalized_task, #find_tasks, #garbage, #garbage_collect, #handle_replace, #include?, #inserted, #inspect, #local_tasks, #locally_useful_tasks, #mission?, #owns?, #partition_event_task, #permanent?, #real_plan, #remote_tasks, #remove_repair, #remove_task, #remove_transaction, #removed_transaction, #repairs_for, #replace_task, #replaced, #respawn, #sibling_on?, #size, #unneeded_events, #unneeded_tasks, #useful_event_component, #useful_events, #useful_task?, #useful_task_component

Methods included from Roby::TaskStructure::ExecutionAgentSpawn

#discovered_tasks

Methods included from Distributed::EventNotifications::PlanCacheCleanup

#finalized_event

Methods included from Distributed::PlanModificationHooks

#discarded, #discovered_events, discovered_objects, #discovered_tasks, #finalized_event, finalized_object, #finalized_task, #inserted, #replaced

Methods included from Roby::Transactions::PlanUpdates

#finalized_event, finalized_object, #finalized_task

Methods included from Propagation::RemoveDelayedOnFinalized

#finalized_event

Methods included from Log::PlanHooks

#added_transaction, #discarded, #discovered_events, #discovered_tasks, #finalized_event, #finalized_task, #garbage, #inserted, #removed_transaction, #replaced

Methods included from EventGenerator::FinalizedEventHook

#finalized_event

Methods inherited from BasicObject

#add_sibling_for, #distribute?, distribute?, #forget_peer, #has_sibling_on?, #initialize_copy, local_only, #read_write?, #remotely_useful?, #remove_sibling_for, #self_owned?, #sibling_of, #sibling_on, #subscribe, #subscribed?, #update_on?, #updated?, #updated_by?, #updated_peers

Methods included from Log::BasicObjectHooks

#added_owner, #removed_owner

Constructor Details

#initialize(plan, options = {}) ⇒ Transaction

Creates a new transaction which applies on plan



206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
# File 'lib/roby/transactions.rb', line 206

def initialize(plan, options = {})
    options = validate_options options, 
	:conflict_solver => :invalidate

    @options = options
    self.conflict_solver = options[:conflict_solver]
    super()

    @plan = plan

    @proxy_objects      = Hash.new
    @removed_objects    = ValueSet.new
    @discarded_tasks    = ValueSet.new
    @auto_tasks	        = ValueSet.new

    Roby::Control.synchronize do
	plan.transactions << self
	plan.added_transaction(self)
    end
end

Instance Attribute Details

#auto_tasksObject (readonly)

The list of permanent tasks that have been auto’ed



184
185
186
# File 'lib/roby/transactions.rb', line 184

def auto_tasks
  @auto_tasks
end

#conflict_solverObject

Returns the value of attribute conflict_solver.



190
191
192
# File 'lib/roby/transactions.rb', line 190

def conflict_solver
  @conflict_solver
end

#discarded_tasksObject (readonly)

The list of discarded



180
181
182
# File 'lib/roby/transactions.rb', line 180

def discarded_tasks
  @discarded_tasks
end

#optionsObject (readonly)

Returns the value of attribute options.



191
192
193
# File 'lib/roby/transactions.rb', line 191

def options
  @options
end

#planObject (readonly)

The plan this transaction applies on



186
187
188
# File 'lib/roby/transactions.rb', line 186

def plan
  @plan
end

#proxy_objectsObject (readonly)

The proxy objects built for this transaction



188
189
190
# File 'lib/roby/transactions.rb', line 188

def proxy_objects
  @proxy_objects
end

#removed_objectsObject (readonly)

The list of removed tasks and events



182
183
184
# File 'lib/roby/transactions.rb', line 182

def removed_objects
  @removed_objects
end

Instance Method Details

#adding_plan_relation(parent, child, relations, info) ⇒ Object



68
69
70
71
72
73
74
75
76
# File 'lib/roby/transactions/updates.rb', line 68

def adding_plan_relation(parent, child, relations, info)
    missing_relations = relations.find_all do |rel|
	!parent.child_object?(child, rel)
    end
    unless missing_relations.empty?
	invalidate("plan added a relation #{parent} -> #{child} in #{relations} with info #{info}")
	conflict_solver.adding_plan_relation(self, parent, child, relations, info)
    end
end

#auto(t) ⇒ Object



267
268
269
270
271
272
273
274
275
276
277
# File 'lib/roby/transactions.rb', line 267

def auto(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
    if proxy = self[t, false]
	super(proxy)
    end

    t = may_unwrap(t)
    if t.plan == self.plan
	auto_tasks.insert(t)
    end
end

#check_valid_transactionObject

Raises:



309
310
311
312
313
314
315
316
317
318
319
# File 'lib/roby/transactions.rb', line 309

def check_valid_transaction
    return if valid_transaction?

    unless transactions.empty?
	raise InvalidTransaction, "there is still transactions on top of this one"
    end
    message = invalidation_reasons.map do |reason, trace|
	"#{trace[0]}: #{reason}\n  #{trace[1..-1].join("\n  ")}"
    end.join("\n")
    raise InvalidTransaction, "invalid transaction: #{message}"
end

#clearObject



453
454
455
456
457
458
459
# File 'lib/roby/transactions.rb', line 453

def clear
    removed_objects.clear
    discarded_tasks.clear
    proxy_objects.each_value { |proxy| proxy.clear_relations }
    proxy_objects.clear
    super
end

#commit_transactionObject

Commit all modifications that have been registered in this transaction



323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
# File 'lib/roby/transactions.rb', line 323

def commit_transaction
    # if !Roby.control.running?
    #     raise "#commit_transaction requires the presence of a control thread"
    # end

    check_valid_transaction
    freezed!

    Roby.execute do
	auto_tasks.each      { |t| plan.auto(t) }
	discarded_tasks.each { |t| plan.discard(t) }
	removed_objects.each do |obj| 
	    plan.remove_object(obj) if plan.include?(obj)
	end

	discover_tasks  = ValueSet.new
	discover_events  = ValueSet.new
	insert    = ValueSet.new
	permanent = ValueSet.new
	known_tasks.dup.each do |t|
	    unwrapped = if t.kind_of?(Transactions::Proxy)
			    finalized_task(t)
			    t.__getobj__
			else
			    known_tasks.delete(t)
			    t
			end

	    if missions.include?(t) && t.self_owned?
		missions.delete(t)
		insert << unwrapped
	    elsif keepalive.include?(t) && t.self_owned?
		keepalive.delete(t)
		permanent << unwrapped
	    end

	    discover_tasks << unwrapped
	end

	free_events.dup.each do |ev|
	    unwrapped = if ev.kind_of?(Transactions::Proxy)
			    finalized_event(ev)
			    ev.__getobj__
			else
			    free_events.delete(ev)
			    ev
			end

	    discover_events << unwrapped
	end

	new_tasks = plan.discover_task_set(discover_tasks)
	new_tasks.each do |task|
	    if task.respond_to?(:commit_transaction)
		task.commit_transaction
	    end
	end

	new_events = plan.discover_event_set(discover_events)
	new_events.each do |event|
	    if event.respond_to?(:commit_transaction)
		event.commit_transaction
	    end
	end

	# Set the plan to nil in known tasks to avoid having the checks on
	# #plan to raise an exception
	proxy_objects.each_value { |proxy| proxy.commit_transaction }
	proxy_objects.each_value { |proxy| proxy.clear_relations  }

	insert.each    { |t| plan.insert(t) }
	permanent.each { |t| plan.permanent(t) }

	proxies     = proxy_objects.dup
	clear
	# Replace proxies by forwarder objects
	proxies.each do |object, proxy|
	    forwarder = Proxy.forwarder(object)
	    forwarder.freeze
	    Kernel.swap! proxy, forwarder
	end

	committed_transaction
	plan.remove_transaction(self)
	@plan = nil

	yield if block_given?
    end
end

#committed_transactionObject



412
# File 'lib/roby/transactions.rb', line 412

def committed_transaction; super if defined? super end

#copy_object_relations(object, proxy) ⇒ Object

This method copies on proxy all relations of object for which both ends of the relation are already in the transaction.



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/roby/transactions.rb', line 40

def copy_object_relations(object, proxy)
    Roby::Control.synchronize do
	# Create edges between the neighbours that are really in the transaction
	object.each_relation do |rel|
	    object.each_parent_object(rel) do |parent|
		if parent_proxy = self[parent, false]
		    parent_proxy.add_child_object(proxy, rel, parent[object, rel])
		end
	    end

	    object.each_child_object(rel) do |child|
		if child_proxy = self[child, false]
		    proxy.add_child_object(child_proxy, rel, object[child, rel])
		end
	    end
	end
    end
end

#disable_proxyingObject



416
417
418
419
420
421
422
423
424
425
# File 'lib/roby/transactions.rb', line 416

def disable_proxying
    @disable_proxying = true
    if block_given?
	begin
	    yield
	ensure
	    @disable_proxying = false
	end
    end
end

#discard(t) ⇒ Object



279
280
281
282
283
284
285
286
287
288
289
# File 'lib/roby/transactions.rb', line 279

def discard(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
    if proxy = self[t, false]
	super(proxy)
    end

    t = may_unwrap(t)
    if t.plan == self.plan
	discarded_tasks.insert(t)
    end
end

#discard_modifications(object) ⇒ Object

Remove proxy from this transaction. While #remove_object is also removing the object from the plan itself, this method only removes it from the transaction, forgetting all modifications that have been done on object in the transaction



96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/roby/transactions.rb', line 96

def discard_modifications(object)
    object = may_unwrap(object)
    if object.respond_to?(:each_plan_child)
	object.each_plan_child do |child|
	    discard_modifications(child)
	end
    end
    removed_objects.delete(object)
    discarded_tasks.delete(object)
    auto_tasks.delete(object)

    return unless proxy = proxy_objects.delete(object)
    proxy.clear_vertex

    missions.delete(proxy)
    known_tasks.delete(proxy)
    free_events.delete(proxy)
end

#discard_transactionObject

Discard all the modifications that have been registered in this transaction



430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
# File 'lib/roby/transactions.rb', line 430

def discard_transaction
#    if !Roby.control.running?
#	raise "#commit_transaction requires the presence of a control thread"
    if !transactions.empty?
	raise InvalidTransaction, "there is still transactions on top of this one"
    end

    freezed!
    proxy_objects.each_value { |proxy| proxy.discard_transaction }
    clear

    discarded_transaction
    Roby.execute do
	plan.remove_transaction(self)
    end
    @plan = nil
end

#discarded_transactionObject



447
# File 'lib/roby/transactions.rb', line 447

def discarded_transaction; super if defined? super end

#discover(objects) ⇒ Object



261
262
263
264
265
# File 'lib/roby/transactions.rb', line 261

def discover(objects)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
    super(self[objects, true])
    self
end

#discover_neighborhood(object) ⇒ Object



227
228
229
230
231
232
233
# File 'lib/roby/transactions.rb', line 227

def discover_neighborhood(object)
    self[object]
    object.each_relation do |rel|
	object.each_parent_object(rel) { |obj| self[obj] }
	object.each_child_object(rel)  { |obj| self[obj] }
    end
end

#do_wrap(object, do_include = false) ⇒ Object

:nodoc:



20
21
22
23
24
25
26
27
28
29
30
31
# File 'lib/roby/transactions.rb', line 20

def do_wrap(object, do_include = false) # :nodoc:
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?

    proxy = proxy_objects[object] = Proxy.proxy_class(object).new(object, self)
    if do_include && object.root_object?
	proxy.plan = self
	discover(proxy)
    end

    copy_object_relations(object, proxy)
    proxy
end

#editObject



34
35
36
# File 'lib/roby/transactions.rb', line 34

def edit
    yield if block_given?
end

#enable_proxyingObject



415
# File 'lib/roby/transactions.rb', line 415

def enable_proxying; @disable_proxying = false end

#executable?Boolean

A transaction is not an executable plan

Returns:

  • (Boolean)


17
# File 'lib/roby/transactions.rb', line 17

def executable?; false end

#finalized?Boolean

Returns:

  • (Boolean)


413
# File 'lib/roby/transactions.rb', line 413

def finalized?; !plan end

#finalized_plan_event(event) ⇒ Object



62
63
64
65
66
# File 'lib/roby/transactions/updates.rb', line 62

def finalized_plan_event(event)
    invalidate("event #{event} has been removed from the plan")
    discard_modifications(event)
    conflict_solver.finalized_plan_event(self, event)
end

#finalized_plan_task(task) ⇒ Object



56
57
58
59
60
# File 'lib/roby/transactions/updates.rb', line 56

def finalized_plan_task(task)
    invalidate("task #{task} has been removed from the plan")
    discard_modifications(task)
    conflict_solver.finalized_plan_task(self, task)
end

#freezed!Object



449
450
451
# File 'lib/roby/transactions.rb', line 449

def freezed!
    @freezed = true
end

#freezed?Boolean

Returns:

  • (Boolean)


18
# File 'lib/roby/transactions.rb', line 18

def freezed?; @freezed end

#insert(t) ⇒ Object



247
248
249
250
251
252
253
# File 'lib/roby/transactions.rb', line 247

def insert(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
    if proxy = self[t, false]
	discarded_tasks.delete(may_unwrap(proxy))
    end
    super(self[t, true]) 
end

#invalid=(flag) ⇒ Object



293
294
295
296
297
298
# File 'lib/roby/transactions.rb', line 293

def invalid=(flag)
    if !flag
	invalidation_reasons.clear
    end
    @invalid = flag
end

#invalid?Boolean

Returns:

  • (Boolean)


300
# File 'lib/roby/transactions.rb', line 300

def invalid?; @invalid end

#invalidate(reason = nil) ⇒ Object



302
303
304
305
306
307
308
# File 'lib/roby/transactions.rb', line 302

def invalidate(reason = nil)
    self.invalid = true
    invalidation_reasons << [reason, caller(1)] if reason
    Roby.debug do
	"invalidating #{self}: #{reason}"
    end
end

#may_unwrap(object) ⇒ Object

may_unwrap may return objects from transaction



166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/roby/transactions.rb', line 166

def may_unwrap(object)
    if object.respond_to?(:plan) 
	if object.plan == self && object.respond_to?(:__getobj__)
	    object.__getobj__
	elsif object.plan == self.plan
	    object
	else
	    object
	end
    else object
    end
end

#may_wrap(object, create = true) ⇒ Object



161
162
163
# File 'lib/roby/transactions.rb', line 161

def may_wrap(object, create = true)
    (wrap(object, create) || object) rescue object 
end

#merged_generated_subgraphs(relation, plan_seeds, transaction_seeds) ⇒ Object

Returns two sets of tasks, [plan, transaction]. The union of the two is the component that would be returned by relation.generated_subgraphs(*seeds) if the transaction was committed



514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
# File 'lib/roby/query.rb', line 514

def merged_generated_subgraphs(relation, plan_seeds, transaction_seeds)
    plan_set        = ValueSet.new
    transaction_set = ValueSet.new
    plan_seeds	      = plan_seeds.to_value_set
    transaction_seeds = transaction_seeds.to_value_set

    loop do
	old_transaction_set = transaction_set.dup
	transaction_set.merge(transaction_seeds)
	for new_set in relation.generated_subgraphs(transaction_seeds, false)
	    transaction_set.merge(new_set)
	end

	if old_transaction_set.size != transaction_set.size
	    for o in (transaction_set - old_transaction_set)
		if o.respond_to?(:__getobj__)
		    o.__getobj__.each_child_object(relation) do |child|
			plan_seeds << child unless self[child, false]
		    end
		end
	    end
	end
	transaction_seeds.clear

	plan_set.merge(plan_seeds)
	plan_seeds.each do |seed|
	    relation.each_dfs(seed, BGL::Graph::TREE) do |_, dest, _, kind|
		next if plan_set.include?(dest)
		if self[dest, false]
		    proxy = wrap(dest, false)
		    unless transaction_set.include?(proxy)
			transaction_seeds << proxy
		    end
		    relation.prune # transaction branches must be developed inside the transaction
		else
		    plan_set << dest
		end
	    end
	end
	break if transaction_seeds.empty?

	plan_seeds.clear
    end

    [plan_set, transaction_set]
end

#permanent(t) ⇒ Object



254
255
256
257
258
259
260
# File 'lib/roby/transactions.rb', line 254

def permanent(t)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?
    if proxy = self[t, false]
	auto_tasks.delete(may_unwrap(proxy))
    end
    super(self[t, true]) 
end

#proposeObject



33
# File 'lib/roby/transactions.rb', line 33

def propose; end

#proxying?Boolean

Returns:

  • (Boolean)


426
# File 'lib/roby/transactions.rb', line 426

def proxying?; !@freezed && !@disable_proxying end

#query_each(result_set) ⇒ Object

Yields tasks in the result set of query. Unlike Query#result_set, all the tasks are included in the transaction



576
577
578
579
580
# File 'lib/roby/query.rb', line 576

def query_each(result_set)
    plan_set, trsc_set = result_set
    plan_set.each { |task| yield(self[task]) }
    trsc_set.each { |task| yield(task) }
end

#query_result_set(matcher) ⇒ Object

Returns [plan_set, transaction_set], where the first is the set of plan tasks matching matcher and the second the set of transaction tasks matching it. The two sets are disjoint.



564
565
566
567
568
569
570
571
572
# File 'lib/roby/query.rb', line 564

def query_result_set(matcher)
    plan_set = ValueSet.new
    for task in plan.query_result_set(matcher)
	plan_set << task unless self[task, false]
    end
    
    transaction_set = super
    [plan_set, transaction_set]
end

#query_roots(result_set, relation) ⇒ Object

Given the result set of query, returns the subset of tasks which have no parent in query



584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
# File 'lib/roby/query.rb', line 584

def query_roots(result_set, relation)
    plan_set      , trsc_set      = *result_set
    plan_result   , trsc_result   = ValueSet.new     , ValueSet.new
    plan_children , trsc_children = ValueSet.new     , ValueSet.new

    for task in plan_set
	next if plan_children.include?(task)
	task_plan_children, task_trsc_children = 
	    merged_generated_subgraphs(relation, [task], [])

	plan_result -= task_plan_children
	trsc_result -= task_trsc_children
	plan_children.merge(task_plan_children)
	trsc_children.merge(task_trsc_children)

	plan_result << task
    end

    for task in trsc_set
	next if trsc_children.include?(task)
	task_plan_children, task_trsc_children = 
	    merged_generated_subgraphs(relation, [], [task])

	plan_result -= task_plan_children
	trsc_result -= task_trsc_children
	plan_children.merge(task_plan_children)
	trsc_children.merge(task_trsc_children)

	trsc_result << task
    end

    [plan_result, trsc_result]
end

#remove_object(object) ⇒ Object



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
# File 'lib/roby/transactions.rb', line 142

def remove_object(object)
    raise "transaction #{self} has been either committed or discarded. No modification allowed" if freezed?

    object = may_unwrap(object)
    proxy = proxy_objects[object] || object

    # removing the proxy may trigger some discovery (event relations
    # for instance, if proxy is a task). Do it first, or #discover
    # will be called and the modifications of internal structures
    # nulled (like #removed_objects) ...
    remove_plan_object(proxy)
    proxy_objects.delete(object)

    if object.plan == self.plan
	# +object+ is new in the transaction
	removed_objects.insert(object)
    end
end

#remove_plan_objectObject



141
# File 'lib/roby/transactions.rb', line 141

alias :remove_plan_object :remove_object

#removing_plan_relation(parent, child, relations) ⇒ Object



78
79
80
81
82
83
84
85
86
# File 'lib/roby/transactions/updates.rb', line 78

def removing_plan_relation(parent, child, relations)
    present_relations = relations.find_all do |rel|
	parent.child_object?(child, rel)
    end
    unless present_relations.empty?
	invalidate("plan removed the #{parent} -> #{child} relation in #{relations}")
	conflict_solver.removing_plan_relation(self, parent, child, relations)
    end
end

#replace(from, to) ⇒ Object



235
236
237
238
239
240
241
242
243
244
245
# File 'lib/roby/transactions.rb', line 235

def replace(from, to)
    # Make sure +from+, its events and all the related tasks and events
    # are in the transaction
    from = may_unwrap(from)
    discover_neighborhood(from)
    from.each_event do |ev|
	discover_neighborhood(ev)
    end

    super(self[from], self[to])
end

#restore_relation(proxy, relation) ⇒ Object



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/roby/transactions.rb', line 115

def restore_relation(proxy, relation)
    object = proxy.__getobj__

    Control.synchronize do
	proxy_children = proxy.child_objects(relation)
	object.child_objects(relation).each do |object_child| 
	    next unless proxy_child = wrap(object_child, false)
	    if proxy_children.include?(proxy_child)
		relation.unlink(proxy, proxy_child)
	    end
	end

	proxy_parents = proxy.parent_objects(relation)
	object.parent_objects(relation).each do |object_parent| 
	    next unless proxy_parent = wrap(object_parent, false)
	    if proxy_parents.include?(proxy_parent)
		relation.unlink(parent, proxy_parent)
	    end
	end
    end

    discovered_objects.delete(proxy)
    proxy.discovered_relations.delete(relation)
    proxy.do_discover(relation, false)
end

#valid_transaction?Boolean

Returns:

  • (Boolean)


301
# File 'lib/roby/transactions.rb', line 301

def valid_transaction?; transactions.empty? && !invalid? end

#wrap(object, create = true) ⇒ Object Also known as: []

Get the transaction proxy for object



60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'lib/roby/transactions.rb', line 60

def wrap(object, create = true)
    if object.kind_of?(PlanObject)
	if object.plan == self then return object
	elsif proxy = proxy_objects[object] then return proxy
	end

	if create
	    if !object.plan
		object.plan = self
		discover(object)
		return object
	    elsif object.plan == self.plan
		wrapped = do_wrap(object, true)
		if plan.mission?(object)
		    insert(wrapped)
		elsif plan.permanent?(object)
		    permanent(wrapped)
		end
		return wrapped
	    else
		raise ArgumentError, "#{object} is in #{object.plan}, this transaction #{self} applies on #{self.plan}"
	    end
	end
	nil
    elsif object.respond_to?(:each) 
	object.map { |o| wrap(o, create) }
    else
	raise TypeError, "don't know how to wrap #{object || 'nil'} of type #{object.class.ancestors}"
    end
end