├── throughputs.ods ├── enter-talk-and-leave.png ├── simply-sending-messages.png ├── enter-while-others-are-talking.png ├── Vagrantfiles └── intellij │ ├── mavenVersion.xml │ ├── git.xml │ ├── project.default.xml │ └── jdk.table.xml ├── hack.sh ├── src ├── main │ └── java │ │ └── net │ │ └── bourgau │ │ └── philippe │ │ └── concurrency │ │ └── kata │ │ ├── common │ │ ├── Output.java │ │ ├── Client.java │ │ ├── Implementation.java │ │ ├── UnsafeRunnable.java │ │ ├── ChatRoom.java │ │ ├── Message.java │ │ ├── SynchronizedChatRoom.java │ │ ├── InProcessClient.java │ │ ├── ConcurrentChatRoom.java │ │ ├── ThreadPoolImplementation.java │ │ ├── UncheckedThrow.java │ │ └── InProcessChatRoom.java │ │ ├── csp │ │ ├── Action.java │ │ ├── ChatRoomCoRoutine.java │ │ ├── ClientCoRoutine.java │ │ ├── ClientAdapter.java │ │ ├── ChatRoomMessages.java │ │ ├── ClientMessages.java │ │ ├── ChatRoomAdapter.java │ │ ├── Channel.java │ │ └── CSP.java │ │ ├── monothread │ │ └── MonoThread.java │ │ ├── unbounded │ │ ├── sync │ │ │ └── UnboundedSync.java │ │ └── concurrent │ │ │ └── UnboundedConcurrent.java │ │ ├── bounded │ │ ├── concurrent │ │ │ └── BoundedConcurrent.java │ │ └── finegrained │ │ │ ├── BoundedFineGrained.java │ │ │ └── FineGrainedChatRoom.java │ │ └── actors │ │ └── threads │ │ ├── green │ │ ├── ActorsGreenThreads.java │ │ ├── Actor.java │ │ ├── ChatRoomActor.java │ │ └── ClientActor.java │ │ └── real │ │ ├── ConcurrentClient.java │ │ └── ActorsRealThreads.java └── test │ └── java │ └── net │ └── bourgau │ └── philippe │ └── concurrency │ └── kata │ ├── NullOutput.java │ ├── MatchingOutput.java │ ├── DotOutput.java │ ├── MemoryOutput.java │ ├── CountingOutput.java │ ├── benchmarks │ ├── EnterTalkAndLeaveBenchmark.java │ ├── EnterWhileTalkingBenchmark.java │ ├── MessageSendingBenchmark.java │ └── BenchmarkTest.java │ ├── Implementations.java │ └── tests │ ├── EndToEndTest.java │ └── ConcurrencyTest.java ├── .gitignore ├── LICENSE ├── Dockerfile ├── pom.xml ├── Vagrantfile └── README.md /throughputs.ods: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philou/concurrency-kata/HEAD/throughputs.ods -------------------------------------------------------------------------------- /enter-talk-and-leave.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philou/concurrency-kata/HEAD/enter-talk-and-leave.png -------------------------------------------------------------------------------- /simply-sending-messages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philou/concurrency-kata/HEAD/simply-sending-messages.png -------------------------------------------------------------------------------- /enter-while-others-are-talking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/philou/concurrency-kata/HEAD/enter-while-others-are-talking.png -------------------------------------------------------------------------------- /Vagrantfiles/intellij/mavenVersion.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /hack.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | ssh-keygen -f "`realpath ~`/.ssh/known_hosts" -R [localhost]:2222 4 | vagrant up 5 | ssh -X -p 2222 vagrant@localhost .idea/bin/idea.sh /vagrant/pom.xml 6 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/Output.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | public interface Output { 4 | void write(String line); 5 | } 6 | -------------------------------------------------------------------------------- /Vagrantfiles/intellij/git.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/Client.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | public interface Client extends Output { 4 | void enter() throws Exception; 5 | 6 | void announce(String message); 7 | 8 | void leave() throws Exception; 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/NullOutput.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Output; 4 | 5 | public class NullOutput implements Output { 6 | @Override 7 | public void write(String line) { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.class 2 | 3 | # Mobile Tools for Java (J2ME) 4 | .mtj.tmp/ 5 | 6 | # Package Files # 7 | *.jar 8 | *.war 9 | *.ear 10 | 11 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 12 | hs_err_pid* 13 | 14 | # emacs files 15 | *~ 16 | 17 | # vagrant files 18 | .vagrant 19 | 20 | # Intellij files 21 | *.iml 22 | .idea 23 | 24 | # maven files 25 | target 26 | 27 | # temp files 28 | *# -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/Implementation.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public interface Implementation { 6 | ChatRoom startNewChatRoom(); 7 | 8 | Client newClient(String name, ChatRoom chatRoom, Output out); 9 | 10 | void awaitOrShutdown(int count, TimeUnit timeUnit) throws InterruptedException; 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/Action.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | public abstract class Action { 4 | 5 | public void execute(T subject) { 6 | try { 7 | doExecute(subject); 8 | 9 | } catch (Exception e) { 10 | e.printStackTrace(); 11 | } 12 | } 13 | 14 | protected abstract void doExecute(T subject) throws Exception; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/UnsafeRunnable.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | public abstract class UnsafeRunnable implements Runnable { 4 | 5 | @Override 6 | public void run() { 7 | try { 8 | doRun(); 9 | 10 | } catch (Exception e) { 11 | e.printStackTrace(); 12 | } 13 | } 14 | 15 | protected abstract void doRun() throws Exception; 16 | } -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/MatchingOutput.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | public class MatchingOutput extends CountingOutput { 4 | private final String subMessage; 5 | 6 | public MatchingOutput(String subMessage) { 7 | this.subMessage = subMessage; 8 | } 9 | 10 | @Override 11 | public void write(String line) { 12 | if (line.contains(subMessage)) { 13 | super.write(line); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/ChatRoom.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public interface ChatRoom { 6 | String GOD_PREFIX = "QPHILIPPEZDFSDFQSRULESDHQSHQS:"; 7 | 8 | void enter(Output client, String pseudo); 9 | 10 | void broadcast(Output client, String message); 11 | 12 | void leave(Output client); 13 | 14 | boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/DotOutput.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Output; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | public class DotOutput implements Output { 8 | private AtomicInteger count = new AtomicInteger(); 9 | 10 | @Override 11 | public void write(String line) { 12 | if (count.incrementAndGet() % 10000 == 0) { 13 | System.out.print("."); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/MemoryOutput.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Output; 4 | 5 | public class MemoryOutput implements Output { 6 | 7 | private final StringBuilder text; 8 | 9 | public MemoryOutput() { 10 | text = new StringBuilder(); 11 | } 12 | 13 | public void write(String line) { 14 | text.append(line).append("\n"); 15 | } 16 | 17 | @Override 18 | public String toString() { 19 | return text.toString(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Vagrantfiles/intellij/project.default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/Message.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | public final class Message { 4 | public static String welcome(String pseudo) { 5 | return String.format("Welcome %s !", pseudo); 6 | } 7 | 8 | public static String signed(String name, String message) { 9 | return String.format("%s > %s", name, message); 10 | } 11 | 12 | public static String exit(String name) { 13 | return String.format("%s left", name); 14 | } 15 | 16 | public static String selfExit() { 17 | return "You left the room"; 18 | } 19 | 20 | public static String login(String message) { 21 | return ChatRoom.GOD_PREFIX + message; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/CountingOutput.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Output; 4 | import org.fest.assertions.api.Assertions; 5 | import org.fest.assertions.api.IntegerAssert; 6 | 7 | import java.util.concurrent.atomic.AtomicInteger; 8 | 9 | public class CountingOutput implements Output { 10 | private AtomicInteger messageCount = new AtomicInteger(); 11 | 12 | @Override 13 | public void write(String line) { 14 | messageCount.incrementAndGet(); 15 | } 16 | 17 | public void reset() { 18 | messageCount.set(0); 19 | } 20 | 21 | public IntegerAssert assertMessageCount() { 22 | return Assertions.assertThat(messageCount.get()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ChatRoomCoRoutine.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | 5 | public class ChatRoomCoRoutine { 6 | 7 | private final Channel> messageChannel; 8 | private final ChatRoom chatRoom; 9 | 10 | public ChatRoomCoRoutine(final ChatRoom chatRoom, Channel> channel) { 11 | this.chatRoom = chatRoom; 12 | this.messageChannel = channel; 13 | } 14 | 15 | public void run() { 16 | messageChannel.pop(new Action>() { 17 | 18 | @Override 19 | protected void doExecute(Action message) throws Exception { 20 | message.execute(chatRoom); 21 | run(); 22 | } 23 | }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/monothread/MonoThread.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.monothread; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.concurrent.TimeUnit; 7 | 8 | public class MonoThread implements Implementation { 9 | 10 | @Override 11 | public InProcessChatRoom startNewChatRoom() { 12 | return new InProcessChatRoom(new HashMap()); 13 | } 14 | 15 | @Override 16 | public Client newClient(String name, ChatRoom chatRoom, Output out) { 17 | return new InProcessClient(name, chatRoom, out); 18 | } 19 | 20 | @Override 21 | public void awaitOrShutdown(int count, TimeUnit timeUnit) throws InterruptedException { 22 | } 23 | 24 | @Override 25 | public String toString() { 26 | return "Mono Threaded"; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/unbounded/sync/UnboundedSync.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.unbounded.sync; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | public class UnboundedSync extends ThreadPoolImplementation { 10 | 11 | @Override 12 | protected ExecutorService newThreadPool() { 13 | return Executors.newCachedThreadPool(); 14 | } 15 | 16 | @Override 17 | protected ChatRoom newChatRoom() { 18 | return new ConcurrentChatRoom( 19 | new SynchronizedChatRoom(new InProcessChatRoom(new HashMap())), 20 | threadPool()); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Unbounded Synchronized"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ClientCoRoutine.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Client; 4 | 5 | public class ClientCoRoutine { 6 | 7 | private final Channel> messageChannel; 8 | private final Client client; 9 | 10 | public ClientCoRoutine(final Client client, Channel> channel) { 11 | this.client = client; 12 | this.messageChannel = channel; 13 | } 14 | 15 | public void run() { 16 | messageChannel.pop(new Action>() { 17 | 18 | @Override 19 | protected void doExecute(Action message) throws Exception { 20 | message.execute(client); 21 | 22 | if (!(message instanceof ClientMessages.Leave)) { 23 | run(); 24 | } 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/unbounded/concurrent/UnboundedConcurrent.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.unbounded.concurrent; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | public class UnboundedConcurrent extends ThreadPoolImplementation { 10 | 11 | @Override 12 | protected ExecutorService newThreadPool() { 13 | return Executors.newCachedThreadPool(); 14 | } 15 | 16 | @Override 17 | public ChatRoom newChatRoom() { 18 | return new ConcurrentChatRoom( 19 | new SynchronizedChatRoom(new InProcessChatRoom(new ConcurrentHashMap())), 20 | threadPool()); 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "Unbounded Concurrent"; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ClientAdapter.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Client; 4 | 5 | public class ClientAdapter implements Client { 6 | 7 | private final Channel> clientChannel; 8 | 9 | public ClientAdapter(Channel> clientChannel) { 10 | this.clientChannel = clientChannel; 11 | } 12 | 13 | @Override 14 | public void write(String line) { 15 | clientChannel.push(ClientMessages.write(line)); 16 | } 17 | 18 | @Override 19 | public void leave() throws Exception { 20 | clientChannel.push(ClientMessages.leave()); 21 | } 22 | 23 | @Override 24 | public void announce(String message) { 25 | clientChannel.push(ClientMessages.announce(message)); 26 | } 27 | 28 | @Override 29 | public void enter() throws Exception { 30 | clientChannel.push(ClientMessages.enter()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/bounded/concurrent/BoundedConcurrent.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.bounded.concurrent; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.concurrent.ConcurrentHashMap; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | import static java.lang.Runtime.getRuntime; 10 | 11 | public class BoundedConcurrent extends ThreadPoolImplementation { 12 | 13 | @Override 14 | protected ExecutorService newThreadPool() { 15 | return Executors.newFixedThreadPool(getRuntime().availableProcessors()); 16 | } 17 | 18 | @Override 19 | public ChatRoom newChatRoom() { 20 | return new ConcurrentChatRoom( 21 | new SynchronizedChatRoom(new InProcessChatRoom(new ConcurrentHashMap())), 22 | threadPool()); 23 | } 24 | 25 | @Override 26 | public String toString() { 27 | return "Bounded Concurrent"; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/SynchronizedChatRoom.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.concurrent.TimeUnit; 4 | 5 | public class SynchronizedChatRoom implements ChatRoom { 6 | 7 | private final ChatRoom realChatRoom; 8 | 9 | public SynchronizedChatRoom(ChatRoom realChatRoom) { 10 | this.realChatRoom = realChatRoom; 11 | } 12 | 13 | @Override 14 | public synchronized void enter(Output client, String pseudo) { 15 | realChatRoom.enter(client, pseudo); 16 | } 17 | 18 | @Override 19 | public synchronized void broadcast(Output client, String message) { 20 | realChatRoom.broadcast(client, message); 21 | } 22 | 23 | @Override 24 | public synchronized void leave(Output client) { 25 | realChatRoom.leave(client); 26 | } 27 | 28 | @Override 29 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 30 | return realChatRoom.waitForAbandon(count, timeUnit); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/bounded/finegrained/BoundedFineGrained.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.bounded.finegrained; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | import net.bourgau.philippe.concurrency.kata.common.ConcurrentChatRoom; 5 | import net.bourgau.philippe.concurrency.kata.common.ThreadPoolImplementation; 6 | 7 | import java.util.concurrent.ExecutorService; 8 | import java.util.concurrent.Executors; 9 | 10 | import static java.lang.Runtime.getRuntime; 11 | 12 | public class BoundedFineGrained extends ThreadPoolImplementation { 13 | 14 | @Override 15 | protected ExecutorService newThreadPool() { 16 | return Executors.newFixedThreadPool(getRuntime().availableProcessors()); 17 | } 18 | 19 | @Override 20 | public ChatRoom newChatRoom() { 21 | return new ConcurrentChatRoom( 22 | new FineGrainedChatRoom(), 23 | threadPool()); 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return "Bounded Fine Grained"; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Philippe Bourgau 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04 2 | MAINTAINER Anatoly Bubenkov "bubenkoff@gmail.com" 3 | 4 | RUN apt-get update 5 | 6 | RUN apt-get install -y openssh-server 7 | 8 | EXPOSE 22 9 | 10 | RUN mkdir -p /var/run/sshd 11 | RUN chmod 0755 /var/run/sshd 12 | 13 | # Create and configure vagrant user 14 | RUN useradd --create-home -s /bin/bash vagrant 15 | WORKDIR /home/vagrant 16 | 17 | # Configure SSH access 18 | RUN mkdir -p /home/vagrant/.ssh 19 | RUN echo "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA6NF8iallvQVp22WDkTkyrtvp9eWW6A8YVr+kz4TjGYe7gHzIw+niNltGEFHzD8+v1I2YJ6oXevct1YeS0o9HZyN1Q9qgCgzUFtdOKLv6IedplqoPkcmF0aYet2PkEDo3MlTBckFXPITAMzF8dJSIFo9D8HfdOV0IAdx4O7PtixWKn5y2hMNG0zQPyUecp4pzC6kivAIhyfHilFR61RGL+GPXQ2MWZWFYbAGjyiYJnAmCP3NOTd0jMZEnDkbUvxhMmBYSdETk1rRgm+R4LOzFUGaHqHDLKLX+FIPKcF96hrucXzcWyLbIbEgE98OHlnVYCzRdK8jlqm8tehUc9c9WhQ== vagrant insecure public key" > /home/vagrant/.ssh/authorized_keys 20 | RUN chown -R vagrant: /home/vagrant/.ssh 21 | RUN echo -n 'vagrant:vagrant' | chpasswd 22 | 23 | # Enable passwordless sudo for the "vagrant" user 24 | RUN echo 'vagrant ALL=NOPASSWD: ALL' > /etc/sudoers.d/vagrant 25 | 26 | 27 | CMD /usr/sbin/sshd -D -o UseDNS=no -o UsePAM=no 28 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/InProcessClient.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | public class InProcessClient implements Client { 4 | 5 | private final ChatRoom chatRoom; 6 | private final String name; 7 | private final Output out; 8 | private boolean entered; 9 | 10 | public InProcessClient(String name, ChatRoom chatRoom, Output out) { 11 | this.chatRoom = chatRoom; 12 | this.name = name; 13 | this.out = out; 14 | } 15 | 16 | @Override 17 | public void enter() throws Exception { 18 | chatRoom.enter(this, name); 19 | entered = true; 20 | } 21 | 22 | @Override 23 | public void announce(String message) { 24 | if (!entered) { 25 | return; 26 | } 27 | chatRoom.broadcast(this, message); 28 | } 29 | 30 | @Override 31 | public void write(String message) { 32 | out.write(message); 33 | } 34 | 35 | @Override 36 | public void leave() throws Exception { 37 | chatRoom.leave(this); 38 | write(Message.selfExit()); 39 | entered = false; 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/green/ActorsGreenThreads.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.green; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | import static java.lang.Runtime.getRuntime; 10 | 11 | public class ActorsGreenThreads extends ThreadPoolImplementation { 12 | 13 | @Override 14 | protected ExecutorService newThreadPool() { 15 | return Executors.newFixedThreadPool(getRuntime().availableProcessors()); 16 | } 17 | 18 | @Override 19 | protected ChatRoom newChatRoom() { 20 | return new ChatRoomActor( 21 | new InProcessChatRoom(new HashMap()), 22 | threadPool()); 23 | } 24 | 25 | @Override 26 | public Client newClient(String name, ChatRoom chatRoom, Output out) { 27 | return new ClientActor(new InProcessClient(name, chatRoom, out), threadPool()); 28 | } 29 | 30 | @Override 31 | public String toString() { 32 | return "Actors with green threads"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/green/Actor.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.green; 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue; 4 | import java.util.concurrent.ExecutorService; 5 | 6 | public class Actor implements Runnable { 7 | protected final ExecutorService threadPool; 8 | private final ConcurrentLinkedQueue mailbox = new ConcurrentLinkedQueue<>(); 9 | private boolean stoped; 10 | 11 | public Actor(ExecutorService threadPool) { 12 | this.threadPool = threadPool; 13 | } 14 | 15 | public void start() { 16 | submitContinuation(); 17 | } 18 | 19 | private void submitContinuation() { 20 | threadPool.submit(this); 21 | } 22 | 23 | public void run() { 24 | if (stoped) { 25 | return; 26 | } 27 | 28 | Runnable nextMessage = mailbox.poll(); 29 | if (nextMessage != null) { 30 | nextMessage.run(); 31 | } 32 | submitContinuation(); 33 | } 34 | 35 | protected void stop() { 36 | stoped = true; 37 | } 38 | 39 | protected void send(Runnable runnable) { 40 | mailbox.add(runnable); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ChatRoomMessages.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | import net.bourgau.philippe.concurrency.kata.common.Output; 5 | 6 | public final class ChatRoomMessages { 7 | 8 | public static Action enter(final Output client, final String pseudo) { 9 | return new Action() { 10 | @Override 11 | public void doExecute(ChatRoom subject) { 12 | subject.enter(client, pseudo); 13 | } 14 | }; 15 | } 16 | 17 | public static Action broadcast(final Output client, final String message) { 18 | return new Action() { 19 | @Override 20 | public void doExecute(ChatRoom subject) { 21 | subject.broadcast(client, message); 22 | } 23 | }; 24 | } 25 | 26 | public static Action leave(final Output client) { 27 | return new Action() { 28 | @Override 29 | public void doExecute(ChatRoom subject) { 30 | subject.leave(client); 31 | } 32 | }; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ClientMessages.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Client; 4 | 5 | public final class ClientMessages { 6 | 7 | public static Action enter() { 8 | return new Action() { 9 | @Override 10 | public void doExecute(Client subject) throws Exception { 11 | subject.enter(); 12 | } 13 | }; 14 | } 15 | 16 | public static Action announce(final String message) { 17 | return new Action() { 18 | @Override 19 | public void doExecute(Client subject) { 20 | subject.announce(message); 21 | } 22 | }; 23 | } 24 | 25 | public static Action write(final String line) { 26 | return new Action() { 27 | @Override 28 | public void doExecute(Client subject) { 29 | subject.write(line); 30 | } 31 | }; 32 | } 33 | 34 | public static Action leave() { 35 | return new Leave(); 36 | } 37 | 38 | public static class Leave extends Action { 39 | @Override 40 | public void doExecute(Client subject) throws Exception { 41 | subject.leave(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/ChatRoomAdapter.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | import net.bourgau.philippe.concurrency.kata.common.InProcessChatRoom; 5 | import net.bourgau.philippe.concurrency.kata.common.Output; 6 | 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class ChatRoomAdapter implements ChatRoom { 10 | 11 | private final Channel> chatRoomChannel; 12 | private final InProcessChatRoom realChatroom; 13 | 14 | public ChatRoomAdapter(InProcessChatRoom realChatroom, Channel> chatRoomChannel) { 15 | this.chatRoomChannel = chatRoomChannel; 16 | this.realChatroom = realChatroom; 17 | } 18 | 19 | @Override 20 | public void enter(Output client, String pseudo) { 21 | chatRoomChannel.push(ChatRoomMessages.enter(client, pseudo)); 22 | } 23 | 24 | @Override 25 | public void broadcast(Output client, String message) { 26 | chatRoomChannel.push(ChatRoomMessages.broadcast(client, message)); 27 | } 28 | 29 | @Override 30 | public void leave(Output client) { 31 | chatRoomChannel.push(ChatRoomMessages.leave(client)); 32 | } 33 | 34 | @Override 35 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 36 | return realChatroom.waitForAbandon(count, timeUnit); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/Channel.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import java.util.concurrent.ConcurrentLinkedQueue; 4 | import java.util.concurrent.ExecutorService; 5 | 6 | public class Channel { 7 | private final ExecutorService threadPool; 8 | private final ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue<>(); 9 | 10 | public Channel(ExecutorService threadPool) { 11 | this.threadPool = threadPool; 12 | } 13 | 14 | void pop(final Action messageHandler) { 15 | /* the caller green thread is blocked until something is 16 | available in the queue. It is the only place where something 17 | is pushed in the queue. This is how the channel maintains the 18 | illusion of the green threads from the caller side 19 | */ 20 | threadPool.submit(new Runnable() { 21 | 22 | @Override 23 | public void run() { 24 | T message = queue.poll(); 25 | if (message != null) { 26 | messageHandler.execute(message); 27 | } else { 28 | threadPool.submit(this); 29 | } 30 | } 31 | }); 32 | } 33 | 34 | void push(T message) { 35 | /* By doing the same kind of trick on the push side, we could 36 | use bounded channels, thus controlling its level of parallelizability 37 | */ 38 | queue.add(message); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/csp/CSP.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.csp; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.HashMap; 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.Executors; 8 | 9 | import static java.lang.Runtime.getRuntime; 10 | 11 | public class CSP extends ThreadPoolImplementation { 12 | 13 | @Override 14 | protected ExecutorService newThreadPool() { 15 | return Executors.newFixedThreadPool(getRuntime().availableProcessors()); 16 | } 17 | 18 | @Override 19 | protected ChatRoom newChatRoom() { 20 | Channel> chatRoomChannel = new Channel<>(threadPool()); 21 | 22 | InProcessChatRoom realChatroom = new InProcessChatRoom(new HashMap()); 23 | 24 | ChatRoomCoRoutine chatRoomChatRoomCoRoutine = new ChatRoomCoRoutine(realChatroom, chatRoomChannel); 25 | chatRoomChatRoomCoRoutine.run(); 26 | 27 | return new ChatRoomAdapter(realChatroom, chatRoomChannel); 28 | } 29 | 30 | @Override 31 | public Client newClient(String name, ChatRoom chatRoom, Output out) { 32 | Channel> clientChannel = new Channel<>(threadPool()); 33 | 34 | ClientCoRoutine clientCoRoutine = new ClientCoRoutine(new InProcessClient(name, chatRoom, out), clientChannel); 35 | clientCoRoutine.run(); 36 | 37 | return new ClientAdapter(clientChannel); 38 | } 39 | 40 | @Override 41 | public String toString() { 42 | return "CSP"; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/ConcurrentChatRoom.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | public class ConcurrentChatRoom implements ChatRoom { 7 | 8 | private final ChatRoom realChatroom; 9 | private final ExecutorService threadPool; 10 | 11 | public ConcurrentChatRoom(ChatRoom realChatroom, ExecutorService threadPool) { 12 | this.realChatroom = realChatroom; 13 | this.threadPool = threadPool; 14 | } 15 | 16 | @Override 17 | public void enter(final Output client, final String pseudo) { 18 | threadPool.submit(new Runnable() { 19 | @Override 20 | public void run() { 21 | realChatroom.enter(client, pseudo); 22 | } 23 | }); 24 | } 25 | 26 | @Override 27 | public void broadcast(final Output client, final String message) { 28 | threadPool.submit(new Runnable() { 29 | @Override 30 | public void run() { 31 | realChatroom.broadcast(client, message); 32 | } 33 | }); 34 | } 35 | 36 | @Override 37 | public void leave(final Output client) { 38 | threadPool.submit(new Runnable() { 39 | @Override 40 | public void run() { 41 | realChatroom.leave(client); 42 | } 43 | }); 44 | } 45 | 46 | @Override 47 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 48 | return realChatroom.waitForAbandon(count, timeUnit); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/benchmarks/EnterTalkAndLeaveBenchmark.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.benchmarks; 2 | 3 | import net.bourgau.philippe.concurrency.kata.Implementations; 4 | import net.bourgau.philippe.concurrency.kata.common.Client; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Parameterized; 7 | 8 | import java.util.Collection; 9 | 10 | import static net.bourgau.philippe.concurrency.kata.Implementations.crossProduct; 11 | import static net.bourgau.philippe.concurrency.kata.Implementations.simple; 12 | 13 | @RunWith(Parameterized.class) 14 | public class EnterTalkAndLeaveBenchmark extends BenchmarkTest { 15 | 16 | @Parameterized.Parameters(name = "{0}, {1} clients") 17 | public static Collection parameters() { 18 | return crossProduct(Implementations.bounded(), simple( 19 | 1, 20 | 10, 21 | 100, 22 | 1000, 23 | 10000)); 24 | } 25 | 26 | @Override 27 | protected void run() throws Exception { 28 | for (Client client : clients) { 29 | client.enter(); 30 | } 31 | for (Client client : clients) { 32 | client.announce("wazzzaaa"); 33 | } 34 | } 35 | 36 | @Override 37 | protected Object measure(double duration) { 38 | double n = clientCount; 39 | double outgoingMessages = n * (n + 2); 40 | return outgoingMessages / duration; 41 | } 42 | 43 | @Override 44 | protected Object scenario() { 45 | return "Enter talk and leave"; 46 | } 47 | 48 | @Override 49 | protected Object scale() { 50 | return clientCount; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/green/ChatRoomActor.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.green; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | import net.bourgau.philippe.concurrency.kata.common.Output; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | import java.util.concurrent.TimeUnit; 8 | 9 | public class ChatRoomActor extends Actor implements ChatRoom { 10 | 11 | private final ChatRoom realChatroom; 12 | 13 | public ChatRoomActor(ChatRoom realChatroom, ExecutorService threadPool) { 14 | super(threadPool); 15 | this.realChatroom = realChatroom; 16 | start(); 17 | } 18 | 19 | @Override 20 | public void enter(final Output client, final String pseudo) { 21 | send(new Runnable() { 22 | @Override 23 | public void run() { 24 | realChatroom.enter(client, pseudo); 25 | } 26 | }); 27 | } 28 | 29 | @Override 30 | public void broadcast(final Output client, final String message) { 31 | send(new Runnable() { 32 | @Override 33 | public void run() { 34 | realChatroom.broadcast(client, message); 35 | } 36 | }); 37 | } 38 | 39 | @Override 40 | public void leave(final Output client) { 41 | send(new Runnable() { 42 | @Override 43 | public void run() { 44 | realChatroom.leave(client); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 51 | return realChatroom.waitForAbandon(count, timeUnit); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/benchmarks/EnterWhileTalkingBenchmark.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.benchmarks; 2 | 3 | import net.bourgau.philippe.concurrency.kata.Implementations; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.Parameterized; 6 | 7 | import java.util.Collection; 8 | 9 | import static net.bourgau.philippe.concurrency.kata.Implementations.crossProduct; 10 | import static net.bourgau.philippe.concurrency.kata.Implementations.simple; 11 | 12 | @RunWith(Parameterized.class) 13 | public class EnterWhileTalkingBenchmark extends BenchmarkTest { 14 | 15 | @Parameterized.Parameters(name = "{0}, {1} clients") 16 | public static Collection parameters() { 17 | return crossProduct(Implementations.bounded(), simple( 18 | 1, 19 | 10, 20 | 100, 21 | 1000, 22 | 1500)); 23 | } 24 | 25 | @Override 26 | protected void run() throws Exception { 27 | for (int iClient = 0; iClient < clients.size(); iClient++) { 28 | clients.get(iClient).enter(); 29 | 30 | for (int jClient = 0; jClient <= iClient; jClient++) { 31 | clients.get(jClient).announce("wazzzaaa"); 32 | } 33 | } 34 | } 35 | 36 | @Override 37 | protected Object measure(double duration) { 38 | double n = clientCount; 39 | double outgoingMessages = n * (n + 1) * (2 * n + 7) / 6; 40 | return outgoingMessages / duration; 41 | } 42 | 43 | @Override 44 | protected Object scenario() { 45 | return "Enter while others are talking"; 46 | } 47 | 48 | @Override 49 | protected Object scale() { 50 | return clientCount; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/green/ClientActor.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.green; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Client; 4 | import net.bourgau.philippe.concurrency.kata.common.UnsafeRunnable; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | 8 | public class ClientActor extends Actor implements Client { 9 | 10 | private final Client realClient; 11 | 12 | public ClientActor(Client realClient, ExecutorService threadPool) { 13 | super(threadPool); 14 | this.realClient = realClient; 15 | start(); 16 | } 17 | 18 | @Override 19 | public void enter() throws Exception { 20 | send(new UnsafeRunnable() { 21 | @Override 22 | public void doRun() throws Exception { 23 | realClient.enter(); 24 | } 25 | }); 26 | } 27 | 28 | @Override 29 | public void announce(final String message) { 30 | send(new UnsafeRunnable() { 31 | @Override 32 | public void doRun() throws Exception { 33 | realClient.announce(message); 34 | } 35 | }); 36 | } 37 | 38 | @Override 39 | public void leave() throws Exception { 40 | send(new UnsafeRunnable() { 41 | @Override 42 | public void doRun() throws Exception { 43 | stop(); 44 | realClient.leave(); 45 | } 46 | }); 47 | } 48 | 49 | @Override 50 | public void write(final String line) { 51 | send(new UnsafeRunnable() { 52 | @Override 53 | public void doRun() throws Exception { 54 | realClient.write(line); 55 | } 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/ThreadPoolImplementation.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.concurrent.ExecutorService; 4 | import java.util.concurrent.TimeUnit; 5 | 6 | public abstract class ThreadPoolImplementation implements Implementation { 7 | private ExecutorService threadPool; 8 | 9 | @Override 10 | public ChatRoom startNewChatRoom() { 11 | threadPool = newThreadPool(); 12 | return newChatRoom(); 13 | } 14 | 15 | protected abstract ExecutorService newThreadPool(); 16 | 17 | protected abstract ChatRoom newChatRoom(); 18 | 19 | protected ExecutorService threadPool() { 20 | return threadPool; 21 | } 22 | 23 | 24 | @Override 25 | public Client newClient(String name, ChatRoom chatRoom, Output out) { 26 | return new InProcessClient(name, chatRoom, out); 27 | } 28 | 29 | @Override 30 | public void awaitOrShutdown(int count, TimeUnit timeUnit) throws InterruptedException { 31 | awaitOrShutdown(threadPool, count, timeUnit); 32 | } 33 | 34 | protected void awaitOrShutdown(ExecutorService threadPool, int count, TimeUnit timeUnit) throws InterruptedException { 35 | threadPool.shutdown(); 36 | try { 37 | if (!threadPool.awaitTermination(count, timeUnit)) { 38 | threadPool.shutdownNow(); 39 | if (!threadPool.awaitTermination(500, TimeUnit.MILLISECONDS)) { 40 | throw new RuntimeException("The thread pool could not force stop all its tasks"); 41 | } 42 | } 43 | 44 | } catch (InterruptedException ie) { 45 | threadPool.shutdownNow(); 46 | Thread.currentThread().interrupt(); 47 | throw ie; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/real/ConcurrentClient.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.real; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.Client; 4 | import net.bourgau.philippe.concurrency.kata.common.UnsafeRunnable; 5 | 6 | import java.util.concurrent.ExecutorService; 7 | 8 | public class ConcurrentClient implements Client { 9 | 10 | private final Client realClient; 11 | private final ExecutorService threadPool; 12 | 13 | public ConcurrentClient(Client realClient, ExecutorService threadPool) { 14 | this.realClient = realClient; 15 | this.threadPool = threadPool; 16 | } 17 | 18 | @Override 19 | public void enter() throws Exception { 20 | threadPool.submit(new UnsafeRunnable() { 21 | @Override 22 | public void doRun() throws Exception { 23 | realClient.enter(); 24 | } 25 | }); 26 | } 27 | 28 | @Override 29 | public void announce(final String message) { 30 | threadPool.submit(new UnsafeRunnable() { 31 | @Override 32 | public void doRun() throws Exception { 33 | realClient.announce(message); 34 | } 35 | }); 36 | } 37 | 38 | @Override 39 | public void leave() throws Exception { 40 | threadPool.submit(new UnsafeRunnable() { 41 | @Override 42 | public void doRun() throws Exception { 43 | realClient.leave(); 44 | } 45 | }); 46 | } 47 | 48 | @Override 49 | public void write(final String line) { 50 | threadPool.submit(new UnsafeRunnable() { 51 | @Override 52 | public void doRun() throws Exception { 53 | realClient.write(line); 54 | } 55 | }); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/actors/threads/real/ActorsRealThreads.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.actors.threads.real; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.*; 4 | 5 | import java.util.ArrayList; 6 | import java.util.HashMap; 7 | import java.util.List; 8 | import java.util.concurrent.ExecutorService; 9 | import java.util.concurrent.Executors; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class ActorsRealThreads extends ThreadPoolImplementation { 13 | 14 | private List clientThreadPools; 15 | 16 | @Override 17 | public ChatRoom startNewChatRoom() { 18 | clientThreadPools = new ArrayList<>(); 19 | return super.startNewChatRoom(); 20 | } 21 | 22 | @Override 23 | protected ExecutorService newThreadPool() { 24 | return Executors.newFixedThreadPool(1); 25 | } 26 | 27 | @Override 28 | protected ChatRoom newChatRoom() { 29 | return new ConcurrentChatRoom( 30 | new InProcessChatRoom(new HashMap()), 31 | threadPool()); 32 | } 33 | 34 | @Override 35 | public Client newClient(String name, ChatRoom chatRoom, Output out) { 36 | ExecutorService threadPool = newThreadPool(); 37 | clientThreadPools.add(threadPool); 38 | return new ConcurrentClient(new InProcessClient(name, chatRoom, out), threadPool); 39 | } 40 | 41 | @Override 42 | public void awaitOrShutdown(int count, TimeUnit timeUnit) throws InterruptedException { 43 | super.awaitOrShutdown(count, timeUnit); 44 | for (ExecutorService clientThreadPool : clientThreadPools) { 45 | awaitOrShutdown(clientThreadPool, 100, TimeUnit.MILLISECONDS); 46 | } 47 | } 48 | 49 | @Override 50 | public String toString() { 51 | return "Actors with real threads"; 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/UncheckedThrow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * JBoss, Home of Professional Open Source 3 | * Copyright 2010, Red Hat Middleware LLC, and individual contributors 4 | * by the @authors tag. See the copyright.txt in the distribution for a 5 | * full listing of individual contributors. 6 | * 7 | * Licensed under the Apache License, Version 2.0 (the "License"); 8 | * you may not use this file except in compliance with the License. 9 | * You may obtain a copy of the License at 10 | * http://www.apache.org/licenses/LICENSE-2.0 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | package net.bourgau.philippe.concurrency.kata.common; 18 | 19 | public final class UncheckedThrow { 20 | private UncheckedThrow() { 21 | } 22 | 23 | public static RuntimeException throwUnchecked(final Throwable ex) { 24 | // Now we use the 'generic' method. Normally the type T is inferred 25 | // from the parameters. However you can specify the type also explicit! 26 | // Now we do just that! We use the RuntimeException as type! 27 | // That means the throwsUnchecked throws an unchecked exception! 28 | // Since the types are erased, no type-information is there to prevent this! 29 | return UncheckedThrow.throwsUnchecked(ex); 30 | } 31 | 32 | /** 33 | * Generics are erased in Java. The real Type of T is lost during the compilation 34 | */ 35 | @SuppressWarnings("unchecked") 36 | private static RuntimeException throwsUnchecked(Throwable toThrow) throws T { 37 | // Since the type is erased, this cast actually does nothing!!! 38 | // we can throw any exception 39 | throw (T) toThrow; 40 | } 41 | } -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | net.bourgau.philippe 8 | concurrency-kata 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 13 | 14 | org.apache.maven.plugins 15 | maven-compiler-plugin 16 | 17 | 1.7 18 | 1.7 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | commons-io 27 | commons-io 28 | 2.4 29 | 30 | 31 | junit 32 | junit 33 | 4.12 34 | test 35 | 36 | 37 | org.easytesting 38 | fest-assert-core 39 | 2.0M10 40 | test 41 | 42 | 43 | com.jayway.awaitility 44 | awaitility 45 | 1.6.3 46 | test 47 | 48 | 49 | commons-lang 50 | commons-lang 51 | 2.6 52 | test 53 | 54 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/benchmarks/MessageSendingBenchmark.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.benchmarks; 2 | 3 | import net.bourgau.philippe.concurrency.kata.Implementations; 4 | import net.bourgau.philippe.concurrency.kata.common.Client; 5 | import org.junit.runner.RunWith; 6 | import org.junit.runners.Parameterized; 7 | 8 | import java.util.Collection; 9 | 10 | import static net.bourgau.philippe.concurrency.kata.Implementations.complex; 11 | import static net.bourgau.philippe.concurrency.kata.Implementations.crossProduct; 12 | 13 | @RunWith(Parameterized.class) 14 | public class MessageSendingBenchmark extends BenchmarkTest { 15 | 16 | @Parameterized.Parameters(name = "{0}, {1} clients each sending {2} messages") 17 | public static Collection parameters() { 18 | return crossProduct(Implementations.bounded(), complex( 19 | new Object[]{100, 1000}, 20 | new Object[]{1000, 10}, 21 | new Object[]{10, 100000}, 22 | new Object[]{1, 1000000}, 23 | new Object[]{10000, 1})); 24 | } 25 | 26 | @Parameterized.Parameter(2) 27 | public int messagePerClientCount; 28 | 29 | public void before_each() throws Exception { 30 | super.before_each(); 31 | 32 | for (Client client : clients) { 33 | client.enter(); 34 | } 35 | } 36 | 37 | @Override 38 | protected void run() throws Exception { 39 | for (int iMessage = 0; iMessage < messagePerClientCount; iMessage++) { 40 | for (Client client : clients) { 41 | client.announce("wazzzaaa"); 42 | } 43 | } 44 | } 45 | 46 | @Override 47 | protected Object measure(double duration) { 48 | double m = messagePerClientCount; 49 | double n = clientCount; 50 | double outgoingMessages = n * (m + (n + 1) / 2); 51 | return outgoingMessages / duration; 52 | } 53 | 54 | @Override 55 | protected Object scenario() { 56 | return "Simply sending messages"; 57 | } 58 | 59 | @Override 60 | protected Object scale() { 61 | return clientCount + " x " + messagePerClientCount; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/benchmarks/BenchmarkTest.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.benchmarks; 2 | 3 | import net.bourgau.philippe.concurrency.kata.NullOutput; 4 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 5 | import net.bourgau.philippe.concurrency.kata.common.Client; 6 | import net.bourgau.philippe.concurrency.kata.common.Implementation; 7 | import net.bourgau.philippe.concurrency.kata.common.Output; 8 | import org.junit.After; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runners.Parameterized; 12 | 13 | import java.io.IOException; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.concurrent.TimeUnit; 17 | 18 | public abstract class BenchmarkTest { 19 | @Parameterized.Parameter(0) 20 | public Implementation implementation; 21 | 22 | @Parameterized.Parameter(1) 23 | public int clientCount; 24 | 25 | protected ChatRoom chatRoom; 26 | protected List clients = new ArrayList<>(); 27 | private Output messageOutput = new NullOutput(); 28 | 29 | @Before 30 | public void before_each() throws Exception { 31 | chatRoom = implementation.startNewChatRoom(); 32 | 33 | for (int i = 0; i < clientCount; i++) { 34 | Client client = implementation.newClient("Client#" + i, chatRoom, messageOutput); 35 | clients.add(client); 36 | } 37 | } 38 | 39 | @After 40 | public void after_each() throws Exception { 41 | implementation.awaitOrShutdown(500, TimeUnit.MILLISECONDS); 42 | } 43 | 44 | @Test(timeout = 60000) 45 | public void benchmark() throws Exception { 46 | double startMillis = System.nanoTime(); 47 | 48 | run(); 49 | 50 | for (Client client : clients) { 51 | client.leave(); 52 | } 53 | 54 | if (!chatRoom.waitForAbandon(50, TimeUnit.SECONDS)) { 55 | printMeasure("Did not finish"); 56 | return; 57 | } 58 | 59 | double duration = (System.nanoTime() - startMillis) * 1E-9; 60 | printMeasure(measure(duration)); 61 | } 62 | 63 | protected abstract Object measure(double duration); 64 | 65 | protected abstract void run() throws Exception; 66 | 67 | protected abstract Object scenario(); 68 | 69 | protected abstract Object scale(); 70 | 71 | private void printMeasure(Object value) throws IOException { 72 | System.out.println(String.format("%s - %s - %s, %s", 73 | scenario(), implementation, scale(), value)); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/common/InProcessChatRoom.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.common; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | import java.util.concurrent.CountDownLatch; 8 | import java.util.concurrent.TimeUnit; 9 | 10 | public class InProcessChatRoom implements ChatRoom { 11 | 12 | private final Map clients; 13 | private final List loginMessages = new ArrayList<>(); 14 | private CountDownLatch abandonLatch = new CountDownLatch(1); 15 | 16 | public InProcessChatRoom(Map emptyClientsMap) { 17 | clients = emptyClientsMap; 18 | } 19 | 20 | @Override 21 | public void enter(Output client, String pseudo) { 22 | clients.put(client, pseudo); 23 | sendLoginMessages(client); 24 | broadcast(Message.welcome(pseudo)); 25 | } 26 | 27 | private void sendLoginMessages(Output client) { 28 | for (String loginMessage : loginMessages) { 29 | client.write(loginMessage); 30 | } 31 | } 32 | 33 | @Override 34 | public void broadcast(Output client, String message) { 35 | boolean loginMessage = isLoginMessage(message); 36 | 37 | if (loginMessage) { 38 | message = cleanLoginMessage(message); 39 | } 40 | 41 | message = Message.signed(clients.get(client), message); 42 | 43 | if (loginMessage) { 44 | storeLoginMessage(message); 45 | } 46 | 47 | broadcast(message); 48 | } 49 | 50 | private String cleanLoginMessage(String message) { 51 | return message.substring(GOD_PREFIX.length()); 52 | } 53 | 54 | private boolean isLoginMessage(String message) { 55 | return message.startsWith(GOD_PREFIX); 56 | } 57 | 58 | private void storeLoginMessage(String message) { 59 | loginMessages.add(message); 60 | } 61 | 62 | private void broadcast(String message) { 63 | for (Output client : new HashMap<>(clients).keySet()) { 64 | safeWrite(client, message); 65 | } 66 | } 67 | 68 | private void safeWrite(Output client, String message) { 69 | try { 70 | client.write(message); 71 | } catch (Exception e) { 72 | leave(client); 73 | } 74 | } 75 | 76 | @Override 77 | public void leave(Output client) { 78 | String pseudo = clients.get(client); 79 | clients.remove(client); 80 | broadcast(Message.exit(pseudo)); 81 | if (clients.size() == 0) { 82 | abandonLatch.countDown(); 83 | } 84 | } 85 | 86 | @Override 87 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 88 | return abandonLatch.await(count, timeUnit); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Vagrantfiles/intellij/jdk.table.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/Implementations.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata; 2 | 3 | import net.bourgau.philippe.concurrency.kata.actors.threads.green.ActorsGreenThreads; 4 | import net.bourgau.philippe.concurrency.kata.actors.threads.real.ActorsRealThreads; 5 | import net.bourgau.philippe.concurrency.kata.bounded.concurrent.BoundedConcurrent; 6 | import net.bourgau.philippe.concurrency.kata.bounded.finegrained.BoundedFineGrained; 7 | import net.bourgau.philippe.concurrency.kata.csp.CSP; 8 | import net.bourgau.philippe.concurrency.kata.monothread.MonoThread; 9 | import net.bourgau.philippe.concurrency.kata.unbounded.concurrent.UnboundedConcurrent; 10 | import net.bourgau.philippe.concurrency.kata.unbounded.sync.UnboundedSync; 11 | import org.apache.commons.lang.ArrayUtils; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | 16 | public class Implementations { 17 | 18 | public static Collection all() { 19 | return simple( 20 | new MonoThread(), 21 | new UnboundedSync(), 22 | new UnboundedConcurrent(), 23 | new BoundedConcurrent(), 24 | new BoundedFineGrained(), 25 | new ActorsRealThreads(), 26 | new ActorsGreenThreads(), 27 | new CSP()); 28 | } 29 | 30 | public static Collection bounded() { 31 | return simple( 32 | new MonoThread(), 33 | new BoundedConcurrent(), 34 | new BoundedFineGrained(), 35 | new ActorsGreenThreads(), 36 | new CSP()); 37 | } 38 | 39 | public static Collection multithreaded() { 40 | return simple( 41 | new UnboundedSync(), 42 | new UnboundedConcurrent(), 43 | new BoundedConcurrent(), 44 | new BoundedFineGrained(), 45 | new ActorsRealThreads(), 46 | new ActorsGreenThreads(), 47 | new CSP()); 48 | } 49 | 50 | public static Collection simple(Object... values) { 51 | ArrayList parameters = new ArrayList<>(); 52 | for (Object value : values) { 53 | parameters.add(new Object[]{value}); 54 | } 55 | return parameters; 56 | } 57 | 58 | public static Collection complex(Object[]... objects) { 59 | ArrayList parameters = new ArrayList<>(); 60 | for (Object[] object : objects) { 61 | parameters.add(object); 62 | } 63 | return parameters; 64 | } 65 | 66 | public static Collection crossProduct(Collection xs, Collection ys) { 67 | ArrayList parameters = new ArrayList<>(); 68 | for (Object[] x : xs) { 69 | for (Object[] y : ys) { 70 | parameters.add(ArrayUtils.addAll(x, y)); 71 | } 72 | } 73 | return parameters; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/main/java/net/bourgau/philippe/concurrency/kata/bounded/finegrained/FineGrainedChatRoom.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.bounded.finegrained; 2 | 3 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 4 | import net.bourgau.philippe.concurrency.kata.common.Message; 5 | import net.bourgau.philippe.concurrency.kata.common.Output; 6 | 7 | import java.util.ArrayList; 8 | import java.util.HashMap; 9 | import java.util.List; 10 | import java.util.Map; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | import java.util.concurrent.CountDownLatch; 13 | import java.util.concurrent.TimeUnit; 14 | 15 | public class FineGrainedChatRoom implements ChatRoom { 16 | 17 | private final Map clients; 18 | private final List loginMessages = new ArrayList<>(); 19 | private CountDownLatch abandonLatch = new CountDownLatch(1); 20 | 21 | public FineGrainedChatRoom() { 22 | clients = new ConcurrentHashMap<>(); 23 | } 24 | 25 | @Override 26 | public synchronized void enter(Output client, String pseudo) { 27 | clients.put(client, pseudo); 28 | sendLoginMessages(client); 29 | broadcast(Message.welcome(pseudo)); 30 | } 31 | 32 | private void sendLoginMessages(Output client) { 33 | for (String loginMessage : loginMessages) { 34 | client.write(loginMessage); 35 | } 36 | } 37 | 38 | @Override 39 | public void broadcast(Output client, String message) { 40 | boolean loginMessage = isLoginMessage(message); 41 | 42 | if (loginMessage) { 43 | message = cleanLoginMessage(message); 44 | } 45 | 46 | message = Message.signed(clients.get(client), message); 47 | 48 | if (loginMessage) { 49 | storeLoginMessage(message); 50 | } 51 | 52 | broadcast(message); 53 | } 54 | 55 | private String cleanLoginMessage(String message) { 56 | return message.substring(GOD_PREFIX.length()); 57 | } 58 | 59 | private boolean isLoginMessage(String message) { 60 | return message.startsWith(GOD_PREFIX); 61 | } 62 | 63 | private synchronized void storeLoginMessage(String message) { 64 | loginMessages.add(message); 65 | } 66 | 67 | private void broadcast(String message) { 68 | for (Output client : new HashMap<>(clients).keySet()) { 69 | safeWrite(client, message); 70 | } 71 | } 72 | 73 | private void safeWrite(Output client, String message) { 74 | try { 75 | client.write(message); 76 | } catch (Exception e) { 77 | leave(client); 78 | } 79 | } 80 | 81 | @Override 82 | public void leave(Output client) { 83 | String pseudo = clients.get(client); 84 | clients.remove(client); 85 | broadcast(Message.exit(pseudo)); 86 | if (clients.size() == 0) { 87 | abandonLatch.countDown(); 88 | } 89 | } 90 | 91 | @Override 92 | public boolean waitForAbandon(long count, TimeUnit timeUnit) throws InterruptedException { 93 | return abandonLatch.await(count, timeUnit); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/tests/EndToEndTest.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.tests; 2 | 3 | import com.jayway.awaitility.Awaitility; 4 | import com.jayway.awaitility.core.ConditionFactory; 5 | import com.jayway.awaitility.core.ConditionTimeoutException; 6 | import net.bourgau.philippe.concurrency.kata.Implementations; 7 | import net.bourgau.philippe.concurrency.kata.MemoryOutput; 8 | import net.bourgau.philippe.concurrency.kata.common.ChatRoom; 9 | import net.bourgau.philippe.concurrency.kata.common.Client; 10 | import net.bourgau.philippe.concurrency.kata.common.Implementation; 11 | import net.bourgau.philippe.concurrency.kata.common.Output; 12 | import org.junit.After; 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.junit.runner.RunWith; 16 | import org.junit.runners.Parameterized; 17 | 18 | import java.util.Collection; 19 | 20 | import static java.util.concurrent.TimeUnit.SECONDS; 21 | import static net.bourgau.philippe.concurrency.kata.common.Message.*; 22 | import static org.fest.assertions.api.Assertions.assertThat; 23 | 24 | @RunWith(Parameterized.class) 25 | public class EndToEndTest { 26 | 27 | @Parameterized.Parameters(name = "{0}") 28 | public static Collection parameters() { 29 | return Implementations.all(); 30 | } 31 | 32 | @Parameterized.Parameter(0) 33 | public Implementation implementation; 34 | 35 | private ChatRoom chatRoom; 36 | private Client joe; 37 | private Client jack; 38 | private Output joeOutput; 39 | private Output jackOutput; 40 | 41 | @Before 42 | public void before_each() throws Exception { 43 | chatRoom = implementation.startNewChatRoom(); 44 | joeOutput = new MemoryOutput(); 45 | jackOutput = new MemoryOutput(); 46 | joe = implementation.newClient("Joe", aClientChatRoom(), joeOutput); 47 | jack = implementation.newClient("Jack", aClientChatRoom(), jackOutput); 48 | 49 | joe.enter(); 50 | joeShouldReceive(welcome("Joe")); 51 | } 52 | 53 | @After 54 | public void after_each() throws Exception { 55 | joe.leave(); 56 | jack.leave(); 57 | } 58 | 59 | protected ChatRoom aClientChatRoom() { 60 | return chatRoom; 61 | } 62 | 63 | @Test 64 | public void 65 | a_client_receives_its_own_welcome_message() throws Exception { 66 | joeShouldReceive(welcome("Joe")); 67 | } 68 | 69 | @Test 70 | public void 71 | a_client_receives_its_own_messages() throws Exception { 72 | joe.announce("Hi everyone !"); 73 | 74 | joeShouldReceive(signed("Joe", "Hi everyone !")); 75 | } 76 | 77 | @Test 78 | public void 79 | a_client_is_notified_of_newcomers() throws Exception { 80 | jack.enter(); 81 | 82 | joeShouldReceive(welcome("Jack")); 83 | } 84 | 85 | @Test 86 | public void 87 | two_clients_can_chat_together() throws Exception { 88 | jack.enter(); 89 | jack.announce("Hi there !"); 90 | 91 | joeShouldReceive(signed("Jack", "Hi there !")); 92 | } 93 | 94 | @Test 95 | public void 96 | a_client_can_leave_the_room() throws Exception { 97 | joe.leave(); 98 | 99 | joeShouldReceive(selfExit()); 100 | } 101 | 102 | @Test 103 | public void 104 | a_client_is_notified_of_the_departure_of_others() throws Exception { 105 | jack.enter(); 106 | jack.leave(); 107 | 108 | joeShouldReceive(exit("Jack")); 109 | } 110 | 111 | @Test(expected = ConditionTimeoutException.class) 112 | public void 113 | a_client_cannot_write_after_it_left() throws Exception { 114 | jack.enter(); 115 | joeShouldReceive(welcome("Jack")); 116 | 117 | jack.leave(); 118 | joeShouldReceive(exit("Jack")); 119 | 120 | jack.announce("All alone now ..."); 121 | 122 | joeShouldReceive(signed("Jack", "Are alone now ...")); 123 | } 124 | 125 | @Test(expected = ConditionTimeoutException.class) 126 | public void 127 | a_client_no_longer_receives_messages_after_it_left() throws Exception { 128 | jack.enter(); 129 | joeShouldReceive(welcome("Jack")); 130 | 131 | joe.leave(); 132 | joeShouldReceive(welcome("Joe")); 133 | 134 | jack.announce("Are you there ?"); 135 | 136 | joeShouldReceive(signed("Jack", "Are you there ?")); 137 | } 138 | 139 | @Test 140 | public void 141 | admins_can_send_login_notice_to_all_clients_using_a_secret_prefix() throws Exception { 142 | jack.enter(); 143 | joeShouldReceive(welcome("Jack")); 144 | 145 | jack.announce(login("You are belong to me !")); 146 | 147 | joeShouldReceive(signed("Jack", "You are belong to me !")); 148 | jackShouldReceive(signed("Jack", "You are belong to me !")); 149 | } 150 | 151 | @Test 152 | public void 153 | new_clients_should_receive_previous_login_message() throws Exception { 154 | joe.announce(login("WARNING: No soccer talk around here !")); 155 | 156 | jack.enter(); 157 | joeShouldReceive(welcome("Jack")); 158 | 159 | jackShouldReceive(signed("Joe", "WARNING: No soccer talk around here !")); 160 | } 161 | 162 | protected void joeShouldReceive(final String message) { 163 | shouldReceive(message, "Joe", joeOutput); 164 | } 165 | 166 | protected void jackShouldReceive(final String message) { 167 | shouldReceive(message, "Joe", jackOutput); 168 | } 169 | 170 | private void shouldReceive(final String message, final String pseudo, final Output output) { 171 | await().until(new Runnable() { 172 | @Override 173 | public void run() { 174 | assertThat(output.toString()).as(pseudo + "'s messages").contains(message); 175 | } 176 | }); 177 | } 178 | 179 | protected static ConditionFactory await() { 180 | return Awaitility.await().atMost(1, SECONDS); 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Vagrantfile: -------------------------------------------------------------------------------- 1 | # -*- mode: ruby -*- 2 | # vi: set ft=ruby : 3 | 4 | #Check if you have the good Vagrant version to use docker provider... 5 | Vagrant.require_version ">= 1.6.0" 6 | 7 | # Vagrantfile API/syntax version. Don't touch unless you know what you're doing! 8 | VAGRANTFILE_API_VERSION = "2" 9 | 10 | # Use docker as default provider 11 | ENV['VAGRANT_DEFAULT_PROVIDER'] = 'docker' 12 | 13 | # This docker config was inspired by https://github.com/bubenkoff/vagrant-docker-example 14 | 15 | # All Vagrant configuration is done below. The "2" in Vagrant.configure 16 | # configures the configuration version (we support older styles for 17 | # backwards compatibility). Please don't change it unless you know what 18 | # you're doing. 19 | Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| 20 | # The most common configuration options are documented and commented below. 21 | # For a complete reference, please see the online documentation at 22 | # https://docs.vagrantup.com. 23 | 24 | # Every Vagrant development environment requires a box. You can search for 25 | # boxes at https://atlas.hashicorp.com/search. 26 | # config.vm.box = "ubuntu/trusty64" 27 | 28 | # Disable automatic box update checking. If you disable this, then 29 | # boxes will only be checked for updates when the user runs 30 | # `vagrant box outdated`. This is not recommended. 31 | # config.vm.box_check_update = false 32 | 33 | # Create a forwarded port mapping which allows access to a specific port 34 | # within the machine from a port on the host machine. In the example below, 35 | # accessing "localhost:8080" will access port 80 on the guest machine. 36 | # config.vm.network "forwarded_port", guest: 80, host: 8080 37 | 38 | # Create a private network, which allows host-only access to the machine 39 | # using a specific IP. 40 | # config.vm.network "private_network", ip: "192.168.33.10" 41 | 42 | # Create a public network, which generally matched to bridged network. 43 | # Bridged networks make the machine appear as another physical device on 44 | # your network. 45 | # config.vm.network "public_network" 46 | 47 | # Share an additional folder to the guest VM. The first argument is 48 | # the path on the host to the actual folder. The second argument is 49 | # the path on the guest to mount the folder. And the optional third 50 | # argument is a set of non-required options. 51 | # config.vm.synced_folder "../data", "/vagrant_data" 52 | 53 | # Provider-specific configuration so you can fine-tune various 54 | # backing providers for Vagrant. These expose provider-specific options. 55 | # Example for VirtualBox: 56 | # 57 | config.vm.provider "docker" do |d| 58 | # The path to a directory containing a Dockerfile. One of this or 59 | # image is required. 60 | d.build_dir = "." 61 | 62 | # If true, then Vagrant will support SSH with the container. This 63 | # allows vagrant ssh to work, provisioners, etc. This defaults to 64 | # false. 65 | d.has_ssh = true 66 | end 67 | 68 | # The port to SSH into. By default this is port 22. 69 | config.ssh.port = 22 70 | 71 | # If true, X11 forwarding over SSH connections is enabled. Defaults to 72 | # false. 73 | config.ssh.forward_x11 = true 74 | 75 | # The path to the private key to use to SSH into the guest machine. By 76 | # default this is the insecure private key that ships with Vagrant, since 77 | # that is what public boxes use. If you make your own custom box with a 78 | # custom SSH key, this should point to that private key. 79 | # You can also specify multiple private keys by setting this to be an array. 80 | # This is useful, for example, if you use the default private key to 81 | # bootstrap the machine, but replace it with perhaps a more secure key later. 82 | config.ssh.private_key_path = "~/.ssh/id_rsa" 83 | 84 | # If true, agent forwarding over SSH connections is enabled. Defaults to false. 85 | config.ssh.forward_agent = true 86 | 87 | # View the documentation for the provider you are using for more 88 | # information on available options. 89 | 90 | # Define a Vagrant Push strategy for pushing to Atlas. Other push strategies 91 | # such as FTP and Heroku are also available. See the documentation at 92 | # https://docs.vagrantup.com/v2/push/atlas.html for more information. 93 | # config.push.define "atlas" do |push| 94 | # push.app = "YOUR_ATLAS_USERNAME/YOUR_APPLICATION_NAME" 95 | # end 96 | 97 | # Enable provisioning with a shell script. Additional provisioners such as 98 | # Puppet, Chef, Ansible, Salt, and Docker are also available. Please see the 99 | # documentation for more information about their specific syntax and use. 100 | config.vm.provision "shell", inline: <<-SHELL 101 | echo "Updating package definitions" 102 | sudo apt-get update 103 | 104 | echo "Installing git and java" 105 | sudo apt-get -y install git openjdk-7-jdk maven 106 | # sudo apt-get -y install openjdk-7-source openjdk-7-dbg 107 | SHELL 108 | 109 | config.vm.provision "file", source: "~/.gitconfig", destination: "$HOME/.gitconfig" 110 | 111 | config.vm.provision "shell", privileged: false, inline: <<-SHELL 112 | if [ ! -d "$HOME/.idea" ]; then 113 | echo "Installing IDEA" 114 | 115 | wget http://download.jetbrains.com/idea/ideaIC-14.1.4.tar.gz 116 | tar -zxvf ideaIC-14.1.4.tar.gz 117 | 118 | mv `find . -maxdepth 1 -type d -and -name "idea-IC-141*"` $HOME/.idea 119 | 120 | sed --in-place 's/-Xmx[0-9]*m/-Xmx2048m/' $HOME/.idea/bin/idea.vmoptions 121 | 122 | echo 'export PATH="$HOME/.idea/bin:$PATH"' >> ~/.bashrc 123 | 124 | mkdir --parents $HOME/.IdeaIC14/config 125 | fi 126 | SHELL 127 | 128 | config.vm.provision "file", source: "Vagrantfiles/intellij/git.xml", destination: "$HOME/.IdeaIC14/config/options/git.xml" 129 | config.vm.provision "file", source: "Vagrantfiles/intellij/jdk.table.xml", destination: "$HOME/.IdeaIC14/config/options/jdk.table.xml" 130 | config.vm.provision "file", source: "Vagrantfiles/intellij/project.default.xml", destination: "$HOME/.IdeaIC14/config/options/project.default.xml" 131 | config.vm.provision "file", source: "Vagrantfiles/intellij/mavenVersion.xml", destination: "$HOME/.IdeaIC14/config/options/mavenVersion.xml" 132 | end 133 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # concurrency-kata 2 | 3 | Didactic walk-through the implementation of a basic chatroom in Java, from crude locks to more abstract concurrency control. 4 | 5 | ## Motivation 6 | 7 | By studying the code here (or even better by redoing the exercice), I hope that one can get a deeper understanding of the various concurrency techniques. I myself learned a lot by preparing this Kata. 8 | 9 | The concurrency techniques are (in order of appearance) : 10 | 11 | * mono-threading 12 | * unbounded thread pool with synchronized methods 13 | * unbounded thread pool with concurrent collections 14 | * bounded thread pool with concurrent collections 15 | * actors model relying on real OS threads 16 | * actors model with green threads, relying on a bounded OS thread pool 17 | * CSP model with green threads 18 | * bounded thread pool with a mix of concurrent collections and fine grained synchronized methods 19 | 20 | ## Installation 21 | 22 | You will need vagrant with docker support. 23 | 24 | Then just start hacking using ```./hack.sh``` the password for vagrant is simply 'vagrant'. 25 | 26 | ### Troubleshooting 27 | 28 | if you are getting errors like 29 | 30 | ``` 31 | /var/run/docker.sock: permission denied. Are you trying to connect to a TLS-enabled daemon without TLS? 32 | ``` 33 | 34 | It means your user is not in the docker group and misses the rights. Please run ```sudo gpasswd -a ${USER} docker``` and restart your machine. See http://techblog.roethof.net/why-running-docker-command-returns-varrundocker-sock-permission-denied/ for details. 35 | 36 | ## How to Follow this Kata 37 | 38 | There are 2 ways to explore this kata : through git or through the IDE. 39 | 40 | ### Through git 41 | 42 | If you have the time, this should teach you all that there is to learn from this kata. You might start from the first MONOTHREAD tag and then study the commits up to master to see how the different implementations were created, refactored and benchmarked. One drawback though is that you might loose some time understanding some implementation details along the way. 43 | 44 | Here are a few commands to do the exploration through git : 45 | 46 | * Having a look at the starting point : ```git checkout MONOTHREAD``` 47 | * To get a summary of the changes : ```git log --decorate --oneline MONOTHREAD..BOUNDED-FINE-GRAINED``` 48 | * Get the details of the commit messges : ```git log --decorate MONOTHREAD..BOUNDED-FINE-GRAINED``` 49 | * Get the details of all the changes in the code : ```git log --decorate --patch MONOTHREAD..BOUNDED-FINE-GRAINED``` 50 | 51 | ### Through the IDE 52 | 53 | This should be the most time effective way to go through this kata. Just checkout specific tags and study the various implementations at this stage. Tags of interest are : 54 | 55 | * CSP : all implementations have been benchmarked, but the chat room does not support login messages, making the bounded concurrent implementation the best by far. 56 | * BOUNDED-FINE-GRAINED : (or directly master) the login feature has been added, with the required fixes to bounded concurrent (corse and fine grained synchronized methods) 57 | 58 | As an example, just run ```git checkout CSP``` to checkout the csp tag 59 | 60 | ## Takeaways 61 | 62 | Here are the final results of one of the benchmarks 63 | 64 | ![Result graphs of benchmark enter while others are talking](https://raw.githubusercontent.com/philou/concurrency-kata/master/enter-while-others-are-talking.png "Result graphs of benchmark enter while others are talking") 65 | 66 | Concerning Java multithreading, here is what I learned or confirmed : 67 | 68 | * If there is not much to parallelize, the mono-thread version will be faster (the chatroom server does not do much, so the benchs are mostly measuring the communication cost, which explains why the monothread version generaly remains the fastest one) 69 | * Spawning an unlimited number of threads will not work. Your system will come to a halt, so unbounded thread pool are only suitable in cases where you know that you will not use too many threads by design. 70 | * Corse grained synchronized blocks are usually slow, but it's an easy way to get thread safety 71 | * If you can get away with a bounded thread pool and concurrent collections, just do it, it's rather simple, and it faster all other implementations hands down 72 | * When things get more complex, it's likely that you'll need to use finer grained synchronized blocks in conjunction with bounded thread pools and concurrent collections. This can still perform very well, but the complexity price is high. This usually becomes very error prone, and there is no way to ensure thread safety (I've heard of horror stories where it took weeks to trigger threading bugs) 73 | * This is when Actors and CSP become an interesting solutions. Actors are really not that difficult to implement in java. Their main advantage being that they are easy to reason about. 74 | * Actors and CSP perform rather well when implemented with the 'green' threads 75 | * Actors are some kind of special case of CSP 76 | * Refactoring Actors to CSP was an interesting problem in itself, and it made me understand CSP a lot better than before 77 | 78 | ## Meta Takeaways 79 | 80 | Preparing this kata, which I thought would take me a few hours of work, turned out to be a large endeavour ... Summing all the work, it took me at least a full week of work. 81 | 82 | I went through a lot of failures and false starts 83 | 84 | * the first implementation had no tests, and I got stuck at some point 85 | * the second used TCP, which was more realistic, but hid the pure threading issues behind the network stack and brought too much complexity 86 | * the third was supposed to demonstrate the kata along the git log, but that proved unrealistic when I later discovered early implementation or test faults which would have required to change the whole git history 87 | * eventually, I got the final one with test, no TCP, all and a branch by abstraction model 88 | 89 | Here is what I learned about preparing this kind of teaching material 90 | 91 | * using branch by abstraction is the best way to compare multiple alternatives in a git repo, because it allows to cope with changes both to the problem and to the implementations at any time 92 | * don't skip test 93 | * be ready to spend a lot of time 94 | 95 | Eventually, here is what I could do with all this 96 | 97 | * practice and distill what I learned even more in order to be able to present all this in a fluid 2h session of live coding (a real Coding Kata) 98 | * extract a 1 or 2 day training about concurrency -------------------------------------------------------------------------------- /src/test/java/net/bourgau/philippe/concurrency/kata/tests/ConcurrencyTest.java: -------------------------------------------------------------------------------- 1 | package net.bourgau.philippe.concurrency.kata.tests; 2 | 3 | import net.bourgau.philippe.concurrency.kata.CountingOutput; 4 | import net.bourgau.philippe.concurrency.kata.Implementations; 5 | import net.bourgau.philippe.concurrency.kata.MatchingOutput; 6 | import net.bourgau.philippe.concurrency.kata.NullOutput; 7 | import net.bourgau.philippe.concurrency.kata.common.*; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | import org.junit.runners.Parameterized; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collection; 15 | import java.util.List; 16 | import java.util.concurrent.CopyOnWriteArrayList; 17 | import java.util.concurrent.CountDownLatch; 18 | 19 | import static com.jayway.awaitility.Awaitility.await; 20 | import static com.jayway.awaitility.Duration.FIVE_SECONDS; 21 | 22 | @RunWith(Parameterized.class) 23 | public class ConcurrencyTest { 24 | 25 | public static final int NB_LOGIN_MESSAGES = 1000; 26 | public static final String LOGIN_MESSAGE = "I am the boss !!!"; 27 | 28 | @Parameterized.Parameters(name = "{0}") 29 | public static Collection parameters() { 30 | return Implementations.multithreaded(); 31 | } 32 | 33 | @Parameterized.Parameter(0) 34 | public Implementation implementation; 35 | 36 | private ChatRoom chatRoom; 37 | private List threads; 38 | private CountDownLatch startLatch; 39 | private CountingOutput countingOutput; 40 | private Client observer; 41 | private List allOutputs; 42 | private Thread moderatorThread; 43 | 44 | @Before 45 | public void setUp() throws Exception { 46 | chatRoom = implementation.startNewChatRoom(); 47 | threads = new ArrayList<>(); 48 | countingOutput = new CountingOutput(); 49 | observer = implementation.newClient("Observer", chatRoom, countingOutput); 50 | allOutputs = new CopyOnWriteArrayList<>(); 51 | 52 | enterObserver(); 53 | } 54 | 55 | private void setNumberOfThreads(int count) { 56 | startLatch = new CountDownLatch(count); 57 | } 58 | 59 | @Test 60 | public void 61 | it_should_handle_many_clients_concurrently() throws Exception { 62 | final int nbClients = 500; 63 | setNumberOfThreads(nbClients); 64 | 65 | for (int i = 0; i < nbClients; i++) { 66 | createThread(new EnterQuit(newClient(i))); 67 | } 68 | 69 | runThreads(); 70 | 71 | await().atMost(FIVE_SECONDS).until(messageCountIs(nbClients * 3)); 72 | } 73 | 74 | @Test 75 | public void 76 | new_clients_should_all_receive_all_login_messages() throws Exception { 77 | final int nbThreads = 2; 78 | setNumberOfThreads(nbThreads + 1); 79 | 80 | moderatorThread = new Thread(moderatorGoneWild()); 81 | moderatorThread.start(); 82 | 83 | for (int i = 0; i < nbThreads; i++) { 84 | createThread(enterClients("Client-" + i + "-")); 85 | } 86 | 87 | runThreads(); 88 | 89 | await().atMost(FIVE_SECONDS).until(allClientsReceivedAtLeast(NB_LOGIN_MESSAGES)); 90 | } 91 | 92 | private Runnable allClientsReceivedAtLeast(final int expectedMessageCount) { 93 | return new Runnable() { 94 | @Override 95 | public void run() { 96 | for (MatchingOutput output : allOutputs) { 97 | output.assertMessageCount().isGreaterThanOrEqualTo(expectedMessageCount); 98 | } 99 | } 100 | }; 101 | } 102 | 103 | private void createThread(Runnable runnable) { 104 | threads.add(new Thread(runnable)); 105 | } 106 | 107 | private ConcurrentRunnable enterClients(final String clientPseudoPrefix) { 108 | return new ConcurrentRunnable() { 109 | @Override 110 | public void go() throws Exception { 111 | int j = 0; 112 | while (moderatorThread.isAlive()) { 113 | final MatchingOutput output = new MatchingOutput(LOGIN_MESSAGE); 114 | Client client = implementation.newClient(clientPseudoPrefix + j, chatRoom, output); 115 | client.enter(); 116 | allOutputs.add(output); 117 | } 118 | } 119 | }; 120 | } 121 | 122 | private ConcurrentRunnable moderatorGoneWild() { 123 | return new ConcurrentRunnable() { 124 | @Override 125 | protected void go() throws Exception { 126 | for (int i = 0; i < NB_LOGIN_MESSAGES; i++) { 127 | observer.announce(Message.login(LOGIN_MESSAGE)); 128 | } 129 | } 130 | }; 131 | } 132 | 133 | private void enterObserver() throws Exception { 134 | observer.enter(); 135 | await().until(messageCountIs(1)); 136 | countingOutput.reset(); 137 | } 138 | 139 | private Client newClient(int i) { 140 | return implementation.newClient("Client#" + i, chatRoom, new NullOutput()); 141 | } 142 | 143 | private Runnable messageCountIs(final int expectedMessageCount) { 144 | return new Runnable() { 145 | @Override 146 | public void run() { 147 | countingOutput.assertMessageCount().isEqualTo(expectedMessageCount); 148 | } 149 | }; 150 | } 151 | 152 | private void runThreads() throws InterruptedException { 153 | for (Thread thread : threads) { 154 | thread.start(); 155 | } 156 | 157 | for (Thread thread : threads) { 158 | thread.join(); 159 | } 160 | } 161 | 162 | private abstract class ConcurrentRunnable extends UnsafeRunnable { 163 | 164 | @Override 165 | public void doRun() throws Exception { 166 | startLatch.countDown(); 167 | startLatch.await(); 168 | 169 | go(); 170 | } 171 | 172 | protected abstract void go() throws Exception; 173 | } 174 | 175 | 176 | private class EnterQuit extends ConcurrentRunnable { 177 | private final Client client; 178 | 179 | public EnterQuit(Client client) { 180 | 181 | this.client = client; 182 | } 183 | 184 | @Override 185 | public void go() throws Exception { 186 | client.enter(); 187 | client.announce("Hi !"); 188 | client.leave(); 189 | } 190 | } 191 | } --------------------------------------------------------------------------------