From d122ff97f34b098aca6d322400b88656fec19d8a Mon Sep 17 00:00:00 2001 From: Jakob Grabner Date: Wed, 1 Aug 2018 08:13:24 +0200 Subject: [PATCH 1/3] Speed up emulator port allocation Storing a list of used ports so we don't need to wait for the emulator start command to finish before starting the next emulator. --- .../kotlin/com/gojuno/swarmer/Emulators.kt | 29 +++++++++---------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt index 4ca96b4..d956faa 100644 --- a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt +++ b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt @@ -33,7 +33,7 @@ fun startEmulators( connectedAdbDevices: () -> Observable> = ::connectedAdbDevices, createAvd: (args: Commands.Start) -> Observable = ::createAvd, applyConfig: (args: Commands.Start) -> Observable = ::applyConfig, - emulator: (args: Commands.Start) -> String = ::emulatorBinary, + emulatorCmd: (args: Commands.Start) -> String = ::emulatorBinary, findAvailablePortsForNewEmulator: () -> Observable> = ::findAvailablePortsForNewEmulator, startEmulatorProcess: (List, Commands.Start) -> Observable = ::startEmulatorProcess, waitForEmulatorToStart: (Commands.Start, () -> Observable>, Observable, Pair) -> Observable = ::waitForEmulatorToStart, @@ -41,10 +41,6 @@ fun startEmulators( ) { val startTime = System.nanoTime() - // Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator, - // so by allocating ports manually we know which serial id emulator will have. - val availablePortsSemaphore = Semaphore(1) - val startedEmulators = connectedAdbDevices() .doOnNext { log("Already running emulators: $it") } .flatMap { @@ -54,12 +50,11 @@ fun startEmulators( args = command, createAvd = createAvd, applyConfig = applyConfig, - availablePortsSemaphore = availablePortsSemaphore, findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator, startEmulatorProcess = startEmulatorProcess, waitForEmulatorToStart = waitForEmulatorToStart, connectedAdbDevices = connectedAdbDevices, - emulator = emulator, + emulatorCmd = emulatorCmd, waitForEmulatorToFinishBoot = waitForEmulatorToFinishBoot ) } @@ -87,29 +82,26 @@ private fun startEmulator( args: Commands.Start, createAvd: (args: Commands.Start) -> Observable, applyConfig: (args: Commands.Start) -> Observable, - availablePortsSemaphore: Semaphore, findAvailablePortsForNewEmulator: () -> Observable>, startEmulatorProcess: (List, Commands.Start) -> Observable, waitForEmulatorToStart: (Commands.Start, () -> Observable>, Observable, Pair) -> Observable, connectedAdbDevices: () -> Observable> = ::connectedAdbDevices, - emulator: (Commands.Start) -> String, + emulatorCmd: (Commands.Start) -> String, waitForEmulatorToFinishBoot: (Emulator, Commands.Start) -> Observable ): Observable = createAvd(args) .flatMap { applyConfig(args) } - .map { availablePortsSemaphore.acquire() } .flatMap { findAvailablePortsForNewEmulator() } .doOnNext { log("Ports for emulator ${args.emulatorName}: ${it.first}, ${it.second}.") } .flatMap { ports -> startEmulatorProcess( // Unix only, PR welcome. - listOf(sh, "-c", "${emulator(args)} ${if (args.verbose) "-verbose" else ""} -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"), + listOf(sh, "-c", "${emulatorCmd(args)} ${if (args.verbose) "-verbose" else ""} -avd ${args.emulatorName} -ports ${ports.first},${ports.second} ${args.emulatorStartOptions.joinToString(" ")} &"), args ).let { process -> waitForEmulatorToStart(args, connectedAdbDevices, process, ports) } } - .map { emulator -> availablePortsSemaphore.release().let { emulator } } .flatMap { emulator -> when (args.redirectLogcatTo) { null -> Observable.just(emulator) @@ -219,17 +211,22 @@ private fun emulatorBinary(args: Commands.Start): String = emulator } +private val usedPorts: MutableList = mutableListOf() +/** + * Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator, + * so by allocating ports manually we know which serial id emulator will have. + */ private fun findAvailablePortsForNewEmulator(): Observable> = connectedAdbDevices() .map { it.filter { it.isEmulator } } .map { - if (it.isEmpty()) { - 5554 - } else { + synchronized(usedPorts) { it .map { it.id } .map { it.substringAfter("emulator-") } .map { it.toInt() } - .max()!! + 2 + .let { it + usedPorts } + .let { (it.max() ?: 5552) + 2 } + .also { usedPorts.add(it) } } } .map { it to it + 1 } From 0718ddc11ae3a933dbd78df6a7fc6fc26f5b2ff4 Mon Sep 17 00:00:00 2001 From: Jakob Grabner Date: Wed, 1 Aug 2018 09:35:14 +0200 Subject: [PATCH 2/3] Fix test --- swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt index e863a08..4a7081e 100644 --- a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt +++ b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt @@ -167,7 +167,7 @@ class EmulatorsSpec : Spek({ connectedAdbDevices = connectedAdbDevices, createAvd = createAvd, applyConfig = applyConfig, - emulator = emulator, + emulatorCmd = emulator, startEmulatorProcess = startEmulatorsProcess, waitForEmulatorToStart = waitForEmulatorToStart, findAvailablePortsForNewEmulator = findAvailablePortsForNewEmulator, From 87af5987a68339c6be9ca4aafc4a323b69c16941 Mon Sep 17 00:00:00 2001 From: Jakob Grabner Date: Sat, 25 Aug 2018 14:45:22 +0200 Subject: [PATCH 3/3] Use atomic int instead of synchronized block. Add test for port allocation --- .../kotlin/com/gojuno/swarmer/Emulators.kt | 26 ++++++++--------- .../com/gojuno/swarmer/EmulatorsSpec.kt | 28 +++++++++++++++++-- 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt index 7a255e6..6da5884 100644 --- a/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt +++ b/swarmer/src/main/kotlin/com/gojuno/swarmer/Emulators.kt @@ -12,10 +12,10 @@ import rx.schedulers.Schedulers import rx.schedulers.Schedulers.io import java.io.File import java.lang.System.nanoTime -import java.util.concurrent.Semaphore import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit.* import java.util.concurrent.TimeoutException +import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicLong val sh: String = "/bin/sh" @@ -34,7 +34,7 @@ fun startEmulators( createAvd: (args: Commands.Start) -> Observable = ::createAvd, applyConfig: (args: Commands.Start) -> Observable = ::applyConfig, emulatorCmd: (args: Commands.Start) -> String = ::emulatorBinary, - findAvailablePortsForNewEmulator: () -> Observable> = ::findAvailablePortsForNewEmulator, + findAvailablePortsForNewEmulator: () -> Observable> = {findAvailablePortsForNewEmulator(connectedAdbDevices)}, startEmulatorProcess: (List, Commands.Start) -> Observable = ::startEmulatorProcess, waitForEmulatorToStart: (Commands.Start, () -> Observable>, Observable, Pair) -> Observable = ::waitForEmulatorToStart, waitForEmulatorToFinishBoot: (Emulator, Commands.Start) -> Observable = ::waitForEmulatorToFinishBoot @@ -225,23 +225,23 @@ private fun emulatorBinary(args: Commands.Start): String = emulator } -private val usedPorts: MutableList = mutableListOf() +private val assignedPortsMax: AtomicInteger = AtomicInteger(5552) /** * Sometimes on Linux "emulator -verbose -avd" does not print serial id of started emulator, * so by allocating ports manually we know which serial id emulator will have. */ -private fun findAvailablePortsForNewEmulator(): Observable> = connectedAdbDevices() +internal fun findAvailablePortsForNewEmulator(connectedAdbDevices:() -> Observable>): Observable> = + connectedAdbDevices() .map { it.filter { it.isEmulator } } .map { - synchronized(usedPorts) { - it - .map { it.id } - .map { it.substringAfter("emulator-") } - .map { it.toInt() } - .let { it + usedPorts } - .let { (it.max() ?: 5552) + 2 } - .also { usedPorts.add(it) } - } + it.map { it.id } + .map { it.substringAfter("emulator-") } + .map { it.toInt() }.max() ?: 5552 + .let { runningEmulatorPortsMax -> + assignedPortsMax.updateAndGet { portMax -> + maxOf(runningEmulatorPortsMax, portMax) + 2 + } + } } .map { it to it + 1 } diff --git a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt index 4a7081e..0d75795 100644 --- a/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt +++ b/swarmer/src/test/kotlin/com/gojuno/swarmer/EmulatorsSpec.kt @@ -8,12 +8,14 @@ import com.nhaarman.mockito_kotlin.argumentCaptor import com.nhaarman.mockito_kotlin.mock import com.nhaarman.mockito_kotlin.verify import com.nhaarman.mockito_kotlin.whenever +import org.assertj.core.api.Assertions.assertThat import org.jetbrains.spek.api.Spek import org.jetbrains.spek.api.dsl.describe import org.jetbrains.spek.api.dsl.it import rx.Completable import rx.Observable import rx.Single +import rx.schedulers.Schedulers import java.io.File import java.util.concurrent.TimeUnit @@ -93,9 +95,10 @@ class EmulatorsSpec : Spek({ ) ) - val EMULATOR_PORTS = Pair(12, 34) - describe("start emulators command called") { + + val EMULATOR_PORTS = Pair(12, 34) + val connectedAdbDevices by memoized { { Observable.just(emptySet()) } } @@ -187,4 +190,25 @@ class EmulatorsSpec : Spek({ } } } + + describe("find available ports for new emulator") { + + val connectedAdbDevices by memoized { + { Observable.just(emptySet()) } + } + + it("does not return duplicated ports") { + + + val testSubscriber = Observable.from(1..100) + .flatMap { + findAvailablePortsForNewEmulator(connectedAdbDevices) + .subscribeOn(Schedulers.io()) + }.test().awaitTerminalEvent() + val allocatedPorts = testSubscriber.onNextEvents + + assertThat(allocatedPorts).doesNotHaveDuplicates() + println("Allocated ports: $allocatedPorts") + } + } }) \ No newline at end of file