Back to Repositories

Testing EventBus Registration Race Conditions in greenrobot/EventBus

This test suite validates EventBus registration and unregistration operations under concurrent conditions, focusing on race condition detection and thread safety. The tests ensure reliable event delivery across multiple threads while maintaining registration state consistency.

Test Coverage Overview

The test suite thoroughly examines concurrent EventBus registration scenarios across multiple threads.

  • Tests registration racing conditions with 16 concurrent threads
  • Validates event delivery to registered subscribers
  • Verifies proper event count across all subscriber threads
  • Tests both registration and unregistration operations

Implementation Analysis

The implementation uses a systematic approach to create controlled concurrent testing conditions.

The test utilizes CountDownLatch synchronization to coordinate thread execution phases and employs a cached thread pool for managing concurrent operations. Each subscriber thread follows a precise lifecycle of registration, event reception, and unregistration.

Technical Details

  • Uses JUnit testing framework
  • Implements ExecutorService for thread management
  • Utilizes CountDownLatch for thread synchronization
  • Configurable iteration count (10 for quick tests, 1000 for long tests)
  • Thread count fixed at 16 concurrent operations

Best Practices Demonstrated

The test demonstrates excellent concurrent testing practices with careful attention to thread synchronization and state management.

  • Proper thread coordination using CountDownLatch
  • Systematic verification of event delivery counts
  • Controlled thread lifecycle management
  • Clear separation of test setup and execution phases

greenrobot/eventbus

EventBusTestJava/src/main/java/org/greenrobot/eventbus/EventBusRegistrationRacingTest.java

            
/*
 * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.eventbus;

import org.junit.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;

import static org.junit.Assert.fail;

/**
 * @author Markus Junginger, greenrobot
 */
public class EventBusRegistrationRacingTest extends AbstractEventBusTest {

    // On a Nexus 5, bad synchronization always failed on the first iteration or went well completely.
    // So a high number of iterations do not guarantee a better probability of finding bugs.
    private static final int ITERATIONS = LONG_TESTS ? 1000 : 10;
    private static final int THREAD_COUNT = 16;

    volatile CountDownLatch startLatch;
    volatile CountDownLatch registeredLatch;
    volatile CountDownLatch canUnregisterLatch;
    volatile CountDownLatch unregisteredLatch;
    
    final Executor threadPool = Executors.newCachedThreadPool();

    @Test
    public void testRacingRegistrations() throws InterruptedException {
        for (int i = 0; i < ITERATIONS; i++) {
            startLatch = new CountDownLatch(THREAD_COUNT);
            registeredLatch = new CountDownLatch(THREAD_COUNT);
            canUnregisterLatch = new CountDownLatch(1);
            unregisteredLatch = new CountDownLatch(THREAD_COUNT);
            
            List<SubscriberThread> threads = startThreads();
            registeredLatch.await();
            eventBus.post("42");
            canUnregisterLatch.countDown();
            for (int t = 0; t < THREAD_COUNT; t++) {
                int eventCount = threads.get(t).eventCount;
                if (eventCount != 1) {
                    fail("Failed in iteration " + i + ": thread #" + t + " has event count of " + eventCount);
                }
            }
            // Wait for threads to be done
            unregisteredLatch.await();
        }
    }

    private List<SubscriberThread> startThreads() {
        List<SubscriberThread> threads = new ArrayList<SubscriberThread>(THREAD_COUNT);
        for (int i = 0; i < THREAD_COUNT; i++) {
            SubscriberThread thread = new SubscriberThread();
            threadPool.execute(thread);
            threads.add(thread);
        }
        return threads;
    }

    public class SubscriberThread implements Runnable {
        volatile int eventCount;

        @Override
        public void run() {
            countDownAndAwaitLatch(startLatch, 10);
            eventBus.register(this);
            registeredLatch.countDown();
            try {
                canUnregisterLatch.await();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            eventBus.unregister(this);
            unregisteredLatch.countDown();
        }

        @Subscribe
        public void onEvent(String event) {
            eventCount++;
        }

    }

}