Skip to content

Commit

Permalink
implemented deadlocking scenario, starving scenario and nolock scenario
Browse files Browse the repository at this point in the history
  • Loading branch information
Tony Baker committed Oct 14, 2012
1 parent 2c2d3d7 commit f4b2795
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 52 deletions.
84 changes: 82 additions & 2 deletions lib/elixir_dining_philosophers.ex
Expand Up @@ -5,11 +5,91 @@ defmodule ElixirDiningPhilosophers do

def dead_locking_scenario do
:ok = :application.start(:elixir_dining_philosophers)
IO.puts "Dead locking!"

fork1 = Fork.create_fork
fork2 = Fork.create_fork
fork3 = Fork.create_fork
fork4 = Fork.create_fork
fork5 = Fork.create_fork

aristotle = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Aristotle", left_fork: fork1, right_fork: fork2))
kant = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Kant", left_fork: fork2, right_fork: fork3))
marx = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Marx", left_fork: fork4, right_fork: fork5))
spinoza = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Spinoza", left_fork: fork3, right_fork: fork4))
russell = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Russell", left_fork: fork5, right_fork: fork1))

philosopher_list = [aristotle, kant, spinoza, marx, russell]

# lock down the simulation to 3 minutes
:timer.sleep(:timer.minutes(3))

Enum.each(philosopher_list, &1 <- {:report_and_exit, self})

wait_for_all_to_exit(philosopher_list)
:ok = :application.stop(:elixir_dining_philosophers)
end

def starving_scenario do
:ok = :application.start(:elixir_dining_philosophers)

fork1 = Fork.create_fork
fork2 = Fork.create_fork
fork3 = Fork.create_fork
fork4 = Fork.create_fork
fork5 = Fork.create_fork

aristotle = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Aristotle", left_fork: fork1, right_fork: fork2))
:timer.sleep(:timer.seconds(1))
marx = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Marx", left_fork: fork4, right_fork: fork5))
kant = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Kant", left_fork: fork2, right_fork: fork3))
:timer.sleep(:timer.seconds(1))
russell = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Russell", left_fork: fork5, right_fork: fork1))
spinoza = Philosopher.DeadLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Spinoza", left_fork: fork3, right_fork: fork4))

philosopher_list = [aristotle, kant, spinoza, marx, russell]

# lock down the simulation to 3 minutes
:timer.sleep(:timer.minutes(3))

Enum.each(philosopher_list, &1 <- {:report_and_exit, self})

wait_for_all_to_exit(philosopher_list)
:ok = :application.stop(:elixir_dining_philosophers)
end

def wait_for_all_to_exit(watch_list) do
all_done = Enum.all?(Enum.map(watch_list, Process.alive?(&1)), &1 == false)

if !all_done do
:timer.sleep(500)
wait_for_all_to_exit(watch_list)
end
end

def non_locking do
:ok = :application.start(:elixir_dining_philosophers)
IO.puts "Non-locking!"

fork1 = Fork.create_fork
fork2 = Fork.create_fork
fork3 = Fork.create_fork
fork4 = Fork.create_fork
fork5 = Fork.create_fork

aristotle = Philosopher.NonLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Aristotle", left_fork: fork1, right_fork: fork2))
kant = Philosopher.NonLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Kant", left_fork: fork2, right_fork: fork3))
spinoza = Philosopher.NonLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Spinoza", left_fork: fork3, right_fork: fork4))
marx = Philosopher.NonLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Marx", left_fork: fork4, right_fork: fork5))
russell = Philosopher.NonLocking.start_philosopher(Philosopher.PhilosopherRecord.new(name: "Russell", left_fork: fork5, right_fork: fork1))

philosopher_list = [aristotle, kant, spinoza, marx, russell]

# lock down the simulation to 3 minutes
:timer.sleep(:timer.minutes(3))

Enum.each(philosopher_list, &1 <- {:report_and_exit, self})

wait_for_all_to_exit(philosopher_list)

:ok = :application.stop(:elixir_dining_philosophers)
end
end
132 changes: 85 additions & 47 deletions lib/philosopher.ex
@@ -1,74 +1,112 @@
defmodule Philosopher do
defrecord Philosopher, name: nil, left_fork: nil, right_fork: nil, status: :waiting, times_eaten: 0
defrecord PhilosopherRecord, name: nil, left_fork: nil, right_fork: nil, status: :waiting, times_eaten: 0

defmodule DeadLocking do
def start_philosopher(philosopher) do
spawn fn -> Philosopher.run_loop(philosopher) end
spawn fn -> run_loop(philosopher) end
end

def run_loop(philosopher) do
# Dead locking philosopher:
# One idea is to instruct each philosopher to behave as follows:
# think until the left fork is available and when it is pick it up
# think until the right fork is available and when it is pick it up
# eat for a fixed amount of time
# put the right fork down
# put the left fork down
# repeat from the beginning

if Fork.is_fork_available(philosopher.left_fork) do
Fork.take_fork(philosopher.left_fork)
IO.puts "#{philosopher.name} took #{philosopher.left_fork}"
else
Philosopher.think(philosopher)
run_loop(philosopher)
end
:random.seed(:erlang.now)

if Fork.is_fork_available(philosopher.right_fork) do
Fork.take_fork(philosopher.right_fork)
IO.puts "#{philosopher.name} took #{philosopher.right_fork}"
else
Philosopher.think(philosopher)
run_loop(philosopher)
end
# Dead locking philosopher:
# One idea is to instruct each philosopher to behave as follows:
# think until the left fork is available and when it is pick it up
# think until the right fork is available and when it is pick it up
# eat for a fixed amount of time
# put the right fork down
# put the left fork down
# repeat from the beginning

if Fork.is_fork_mine(philosopher.left_fork) and Fork.is_fork_mine(philosopher.right_fork) do
Philosopher.eat(philosopher)
Fork.return_fork(philosopher.left_fork)
IO.puts "#{philosopher.name} returned #{philosopher.left_fork}"
if Fork.is_fork_available(philosopher.left_fork) do
Fork.take_fork(philosopher.left_fork)
else
Philosopher.think(philosopher)
end

Fork.return_fork(philosopher.right_fork)
IO.puts "#{philosopher.name} returned #{philosopher.right_fork}"
end
if Fork.is_fork_available(philosopher.right_fork) do
Fork.take_fork(philosopher.right_fork)
else
Philosopher.think(philosopher)
end

if Fork.is_fork_mine(philosopher.left_fork) and Fork.is_fork_mine(philosopher.right_fork) do
philosopher = Philosopher.eat(philosopher)
Fork.return_fork(philosopher.left_fork)
Fork.return_fork(philosopher.right_fork)
end

receive do
{:report_and_exit, from} ->
IO.puts("**** #{philosopher.name} has eaten #{philosopher.times_eaten}\n")
Fork.return_fork(philosopher.left_fork)
Fork.return_fork(philosopher.right_fork)
Process.exit(self, :kill)
after 0 ->
run_loop(philosopher)
end
end
end


# defimpl NonLocking do
# def create_philosopher()
# def start_philosopher(left_fork, right_fork)
# non dead locking philosopher
# Each philosopher flips a coin.
# Heads, he picks up the right chopstick first, tails, the left.
# If the second chopstick is busy, he puts down the first and tries again.
# With probability 1, he will eventually eat.
# Again, this solution relies on defeating circular waiting whenever possible and then resorts to breaking 'acquiring while holding' as assurance for the case when two adjacent philosophers' coins both come up the same.
# Again, this solution is fair and ensures all philosophers can eat eventually.
# end
defmodule NonLocking do
def start_philosopher(philosopher) do
spawn fn -> run_loop(philosopher) end
end

def run_loop(philosopher) do
:random.seed(:erlang.now)

# Each philosopher flips a coin.
# Heads, he picks up the right chopstick first, tails, the left.
# If the second chopstick is busy, he puts down the first and tries again.
# With probability 1, he will eventually eat.
# Again, this solution relies on defeating circular waiting whenever possible and then resorts to breaking 'acquiring while holding' as assurance for the case when two adjacent philosophers' coins both come up the same.
# Again, this solution is fair and ensures all philosophers can eat eventually.

right_first = :random.uniform(2) == 1
first_fork = philosopher.right_fork
second_fork = philosopher.left_fork

if !right_first do
first_fork = philosopher.left_fork
second_fork = philosopher.right_fork
end

if Fork.take_fork(first_fork) do
if Fork.take_fork(second_fork) do
philosopher = Philosopher.eat(philosopher)

Fork.return_fork(second_fork)
Fork.return_fork(first_fork)
else
Fork.return_fork(first_fork)
Philosopher.think(philosopher)
end
end

receive do
{:report_and_exit, from} ->
IO.puts("**** #{philosopher.name} has eaten #{philosopher.times_eaten}\n")
Fork.return_fork(philosopher.left_fork)
Fork.return_fork(philosopher.right_fork)
Process.exit(self, :kill)
after 0 ->
run_loop(philosopher)
end
end
end

def think(philosopher) do
time = :random.uniform(10) * 1000
IO.puts("#{philosopher.name} is sleeping for #{time} seconds")
time = :random.uniform(2000)
IO.puts("#{philosopher.name} is thinking for #{time} milliseconds\n")

:timer.sleep(time)
end

def eat(philosopher) do
time = :random.uniform(10) * 1000
IO.puts("#{philosopher.name} is eating for #{time} seconds")
time = :random.uniform(2000)
IO.puts("#{philosopher.name} is eating for #{time} milliseconds, #{philosopher.name} has eaten #{philosopher.times_eaten}\n")

:timer.sleep(time)
philosopher.increment_times_eaten
Expand Down
19 changes: 18 additions & 1 deletion mix.exs
Expand Up @@ -20,6 +20,23 @@ defmodule ElixirDiningPhilosophers.Mixfile do
end

defmodule Mix.Tasks.Start do
@shortdoc "use mix start.deadlock | start.nolock | start.starving"
@moduledoc "use mix start.deadlock | start.nolock | start.starving"

def run(_) do
IO.puts "use mix start.deadlock | start.nolock | start.starving"
end

defmodule Starving do
use Mix.Task

@shortdoc "start up the starving scenario"

def run(_) do
Mix.Task.run("run", ["ElixirDiningPhilosophers.starving_scenario()"])
end
end

defmodule Deadlock do
use Mix.Task

Expand All @@ -30,7 +47,7 @@ defmodule Mix.Tasks.Start do
end
end

defmodule Nonlocking do
defmodule Nolock do
use Mix.Task

@shortdoc "start up the non locking scenario"
Expand Down
10 changes: 8 additions & 2 deletions test/elixir_dining_philosophers_test.exs
Expand Up @@ -57,16 +57,22 @@ defmodule ElixirDiningPhilosophersTest do
assert !Fork.is_fork_taken(fork2)
assert Fork.is_fork_available(fork2)

Fork.take_fork(fork1)
assert Fork.take_fork(fork1)

assert Fork.is_fork_taken(fork1)
assert !Fork.is_fork_taken(fork2)

Fork.take_fork(fork2)
assert Fork.take_fork(fork2)
assert Fork.is_fork_taken(fork1)
assert Fork.is_fork_taken(fork2)
end

test "forks can not be taken twice" do
fork1 = Fork.create_fork
assert Fork.take_fork(fork1)
assert !Fork.take_fork(fork1)
end

test "forks can be returned" do
fork1 = Fork.create_fork
fork2 = Fork.create_fork
Expand Down

0 comments on commit f4b2795

Please sign in to comment.