/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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.apache.usergrid.persistence.collection.rx; import java.util.ArrayList; import java.util.List; import java.util.concurrent.CountDownLatch; import org.junit.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import rx.Observable; import rx.functions.Func1; import rx.functions.FuncN; import rx.schedulers.Schedulers; import static org.junit.Assert.assertEquals; /** * Tests that provides examples of how to perform more complex RX operations */ public class ParallelTest { private static final Logger logger = LoggerFactory.getLogger( ParallelTest.class ); // // private static final HystrixCommandGroupKey GROUP_KEY = HystrixCommandGroupKey.Factory.asKey( "TEST_KEY" ); // // // public static final String THREAD_POOL_SIZE = CommandUtils.getThreadPoolCoreSize( GROUP_KEY.name() ); // // public static final String THREAD_POOL_QUEUE = CommandUtils.getThreadPoolMaxQueueSize( GROUP_KEY.name() ); /** * An example of how an observable that requires a "fan out" then join should execute. */ @Test(timeout = 5000) public void concurrentFunctions() { final String input = "input"; final int size = 100; //since we start at index 0 final int expected = size - 1; // QUESTION Using this thread blocks indefinitely. The execution of the Hystrix command // happens on the computation Thread if this is used // final Scheduler scheduler = Schedulers.threadPoolForComputation(); //use the I/O scheduler to allow enough thread, otherwise our pool will be the same size as the # of cores //set our size equal // ConfigurationManager.getConfigInstance().setProperty( THREAD_POOL_SIZE, size ); // ConfigurationManager.getConfigInstance().setProperty( THREAD_POOL_SIZE, 10 ); //reject requests we have to queue // ConfigurationManager.getConfigInstance().setProperty( THREAD_POOL_QUEUE, -1 ); //latch used to make each thread block to prove correctness final CountDownLatch latch = new CountDownLatch( size ); //create our observable and execute it in the I/O pool since we'll be doing I/O operations /** * QUESTION: Should this use the computation scheduler since all operations (except the hystrix command) are * non blocking? */ final Observable<String> observable = Observable.just( input ).observeOn( Schedulers.io() ); Observable<Integer> thing = observable.flatMap( new Func1<String, Observable<Integer>>() { @Override public Observable<Integer> call( final String s ) { List<Observable<Integer>> functions = new ArrayList<Observable<Integer>>(); logger.info( "Creating new set of observables in thread {}", Thread.currentThread().getName() ); for ( int i = 0; i < size; i++ ) { final int index = i; // create a new observable and execute the function on it. // These should happen in parallel when a subscription occurs /** * QUESTION: Should this again be the process thread, not the I/O */ Observable<String> newObservable = Observable.just( input ).subscribeOn( Schedulers.io() ); Observable<Integer> transformed = newObservable.map( new Func1<String, Integer>() { @Override public Integer call( final String s ) { final String threadName = Thread.currentThread().getName(); logger.info( "Invoking parallel task in thread {}", threadName ); // /** // * Simulate a Hystrix command making a call to an external resource. Invokes // * the Hystrix command immediately as the function is invoked. This is currently // * how we have to call Cassandra. // * // * TODO This needs to be re-written and evaluated once this PR is released https://github.com/Netflix/Hystrix/pull/209 // */ // return new HystrixCommand<Integer>( GROUP_KEY ) { // @Override // protected Integer run() throws Exception { // // final String threadName = Thread.currentThread().getName(); // // logger.info( "Invoking hystrix task in thread {}", threadName ); latch.countDown(); try { latch.await(); } catch ( InterruptedException e ) { throw new RuntimeException( "Interrupted", e ); } // assertTrue( isExecutedInThread() ); // // return index; // } // }.execute(); return index; } } ); functions.add( transformed ); } /** * Execute the functions above and zip the results together */ Observable<Integer> zipped = Observable.zip( functions, new FuncN<Integer>() { @Override public Integer call( final Object... args ) { logger.info( "Invoking zip in thread {}", Thread.currentThread().getName() ); assertEquals( size, args.length ); for ( int i = 0; i < args.length; i++ ) { assertEquals( "Indexes are returned in order", i, args[i] ); } //just return our string return ( Integer ) args[args.length - 1]; } } ); return zipped; } } ); final Integer last = thing.toBlocking().last(); assertEquals( expected, last.intValue() ); } }