Posts mit dem Label Concurrency werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Concurrency werden angezeigt. Alle Posts anzeigen

Mittwoch, 10. November 2010

Secure your steps, how to disengage from a move action

Update: This feature has been removed in with the beginning of the 3rd season. Instead use look_at for agents in level Clone War.

In Zero X the simulation and all agents are running continuously. That's one big difference to other programming games, that usually are round based. The consequence is, that in the time between an agent inspects his environment with a look_around and the action he takes, with a move_to for example, the environment can change.
For example a field is free, when he makes the look_around, but has been occupied in the meanwhile by another agent, when he moves. This would result in a unintended attack against this other agent.

Although this case is rare, as a look_around and a move_to are very close. But this time gape can grow bigger as the analysis of the environment takes more time. In general agents have to deal with this kind of insecurity about their perception. This one of the many challenges in this game.

But in order to handle better this kind of insecurity, starting with season 2, move_to can now take a block.
move_to(x,y) {|target| ... }
The argument passed to the block is another view of the target field at the exact time of the movement. This gives the agent the chance to check the preconditions of his action. If they are not fulfilled anymore, he can now disengage from the move action.

move_to(x,y) do |target|
  unless target.has_no_population?
     disengage 'field is not free anymore!'
  end
end
Disengage can take a message, that will then be displayed in your reports. The action is canceled and the agent remains on his starting field. But disengage comes not for free. A move_to would cost 6 action points, disengage from this action still costs 3 action points.

Disengage can only be used within an action block like the one in move_to.

Mittwoch, 26. August 2009

Nested Synchronize Blocks

In my new simulation project I have different threads accessing objects. The simulation server calculates periodically their states and the tournament server, handling all agents, is accessing them over distributed Ruby. Therefore I need to synchronize the sim objects. My first attempt looked something like this:
class SyncObject

  def initialize
    @semaphore = Mutex.new
  end

  def synchronize &block
    @semaphore.synchronize &block
  end

end
Before changing something in the sim objects, I synchronize them :
  object.synchronize do
    # change the state of the object
  end
But as things were growing, I suddently got some deadlocks. After some research I found out, that a thread was invoking synchronize on the same object multiple times. The semaphore object doesn't care which thread is locking it. As long it's locked it will block all threads until the block is finished. Even if it's the same thread, that locked the semaphore just before.
But I needed a different behaviour, I want to synchronize the objects from different threads. As far as I remember Java's synchronize behaves the same way.
Using rspec we could specify this behaviour with 2 tests:
it "should not block the same thread when synchronizing" do
    object = SyncObject.new
    object.synchronize do
      object.synchronize do
        object.synchronize do
          true.should be_true
        end
      end
    end
  end
it "should block two different threads when synchronizing" do
    object = SyncObject.new
    wait_for_me = true
    object.synchronize do
      Thread.fork do
        object.synchronize do
          wait_for_me = false
        end
      end
      sleep 1
      wait_for_me.should be_true
    end
    wait_for_me.should be_false
  end
The first test's pretty straight forward, we want to allow nested synchronize blocks for the same thread.
For the second test we need two different threads. The first thread locks the object and lunches a second thread. The first one returns immediately after fork, while the second one is launched in the meantime. So while the first is waiting for a second, the second thread is trying to achieve the lock and is blocked until the first one releases it on line 12. Only now the second thread gets access to the object and can set wait_for_me to false.
Of course both tests fail (or get some deadlocks) with the implemention above. The second version of synchronize looks now like this:
class SyncObject

  def initialize
    @semaphore = Mutex.new
  end

  def synchronize
    unless @current_thread == Thread.current
      @semaphore.synchronize do
        @current_thread = Thread.current
        yield
        @current_thread = nil
      end
    else
      yield
    end
  end

end
The first time synchronize is accessed, @current_thread is set to the current running thread. The semaphore is locked and the blocked yield. If in the meantime a different thread invokes synchronize, it will be blocked until the first one finishes the block. Else if the same thread access the method in the meantime, it will not try to lock the semaphore again, but can yield its new block immediately.