Skip to content

Commit c856651

Browse files
mateizrxin
authored andcommitted
Merge pull request alteryx#164 from tdas/kafka-fix
Made block generator thread safe to fix Kafka bug. This is a very important bug fix. Data can and was being lost in the kafka due to this. (cherry picked from commit dfd1ebc) Signed-off-by: Reynold Xin <rxin@apache.org>
1 parent 30786c6 commit c856651

2 files changed

Lines changed: 80 additions & 7 deletions

File tree

streaming/src/main/scala/org/apache/spark/streaming/dstream/NetworkInputDStream.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -232,11 +232,11 @@ abstract class NetworkReceiver[T: ClassManifest]() extends Serializable with Log
232232
logInfo("Data handler stopped")
233233
}
234234

235-
def += (obj: T) {
235+
def += (obj: T): Unit = synchronized {
236236
currentBuffer += obj
237237
}
238238

239-
private def updateCurrentBuffer(time: Long) {
239+
private def updateCurrentBuffer(time: Long): Unit = synchronized {
240240
try {
241241
val newBlockBuffer = currentBuffer
242242
currentBuffer = new ArrayBuffer[T]

streaming/src/test/scala/org/apache/spark/streaming/InputStreamsSuite.scala

Lines changed: 78 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,15 +23,15 @@ import akka.actor.IOManager
2323
import akka.actor.Props
2424
import akka.util.ByteString
2525

26-
import dstream.SparkFlumeEvent
26+
import org.apache.spark.streaming.dstream.{NetworkReceiver, SparkFlumeEvent}
2727
import java.net.{InetSocketAddress, SocketException, Socket, ServerSocket}
2828
import java.io.{File, BufferedWriter, OutputStreamWriter}
29-
import java.util.concurrent.{TimeUnit, ArrayBlockingQueue}
29+
import java.util.concurrent.{Executors, TimeUnit, ArrayBlockingQueue}
3030
import collection.mutable.{SynchronizedBuffer, ArrayBuffer}
3131
import util.ManualClock
3232
import org.apache.spark.storage.StorageLevel
3333
import org.apache.spark.streaming.receivers.Receiver
34-
import org.apache.spark.Logging
34+
import org.apache.spark.{SparkContext, Logging}
3535
import scala.util.Random
3636
import org.apache.commons.io.FileUtils
3737
import org.scalatest.BeforeAndAfter
@@ -44,6 +44,7 @@ import java.nio.ByteBuffer
4444
import collection.JavaConversions._
4545
import java.nio.charset.Charset
4646
import com.google.common.io.Files
47+
import java.util.concurrent.atomic.AtomicInteger
4748

4849
class InputStreamsSuite extends TestSuiteBase with BeforeAndAfter {
4950

@@ -61,7 +62,6 @@ class InputStreamsSuite extends TestSuiteBase with BeforeAndAfter {
6162
System.clearProperty("spark.hostPort")
6263
}
6364

64-
6565
test("socket input stream") {
6666
// Start the server
6767
val testServer = new TestServer()
@@ -271,10 +271,49 @@ class InputStreamsSuite extends TestSuiteBase with BeforeAndAfter {
271271
val kafkaParams = Map("zk.connect"->"localhost:12345","groupid"->"consumer-group")
272272
val test3 = ssc.kafkaStream[String, kafka.serializer.StringDecoder](kafkaParams, topics, StorageLevel.MEMORY_AND_DISK)
273273
}
274+
275+
test("multi-thread receiver") {
276+
// set up the test receiver
277+
val numThreads = 10
278+
val numRecordsPerThread = 1000
279+
val numTotalRecords = numThreads * numRecordsPerThread
280+
val testReceiver = new MultiThreadTestReceiver(numThreads, numRecordsPerThread)
281+
MultiThreadTestReceiver.haveAllThreadsFinished = false
282+
283+
// set up the network stream using the test receiver
284+
val ssc = new StreamingContext(master, framework, batchDuration)
285+
val networkStream = ssc.networkStream[Int](testReceiver)
286+
val countStream = networkStream.count
287+
val outputBuffer = new ArrayBuffer[Seq[Long]] with SynchronizedBuffer[Seq[Long]]
288+
val outputStream = new TestOutputStream(countStream, outputBuffer)
289+
def output = outputBuffer.flatMap(x => x)
290+
ssc.registerOutputStream(outputStream)
291+
ssc.start()
292+
293+
// Let the data from the receiver be received
294+
val clock = ssc.scheduler.clock.asInstanceOf[ManualClock]
295+
val startTime = System.currentTimeMillis()
296+
while((!MultiThreadTestReceiver.haveAllThreadsFinished || output.sum < numTotalRecords) &&
297+
System.currentTimeMillis() - startTime < 5000) {
298+
Thread.sleep(100)
299+
clock.addToTime(batchDuration.milliseconds)
300+
}
301+
Thread.sleep(1000)
302+
logInfo("Stopping context")
303+
ssc.stop()
304+
305+
// Verify whether data received was as expected
306+
logInfo("--------------------------------")
307+
logInfo("output.size = " + outputBuffer.size)
308+
logInfo("output")
309+
outputBuffer.foreach(x => logInfo("[" + x.mkString(",") + "]"))
310+
logInfo("--------------------------------")
311+
assert(output.sum === numTotalRecords)
312+
}
274313
}
275314

276315

277-
/** This is server to test the network input stream */
316+
/** This is a server to test the network input stream */
278317
class TestServer() extends Logging {
279318

280319
val queue = new ArrayBlockingQueue[String](100)
@@ -336,6 +375,7 @@ object TestServer {
336375
}
337376
}
338377

378+
/** This is an actor for testing actor input stream */
339379
class TestActor(port: Int) extends Actor with Receiver {
340380

341381
def bytesToString(byteString: ByteString) = byteString.utf8String
@@ -347,3 +387,36 @@ class TestActor(port: Int) extends Actor with Receiver {
347387
pushBlock(bytesToString(bytes))
348388
}
349389
}
390+
391+
/** This is a receiver to test multiple threads inserting data using block generator */
392+
class MultiThreadTestReceiver(numThreads: Int, numRecordsPerThread: Int)
393+
extends NetworkReceiver[Int] {
394+
lazy val executorPool = Executors.newFixedThreadPool(numThreads)
395+
lazy val blockGenerator = new BlockGenerator(StorageLevel.MEMORY_ONLY)
396+
lazy val finishCount = new AtomicInteger(0)
397+
398+
protected def onStart() {
399+
blockGenerator.start()
400+
(1 to numThreads).map(threadId => {
401+
val runnable = new Runnable {
402+
def run() {
403+
(1 to numRecordsPerThread).foreach(i =>
404+
blockGenerator += (threadId * numRecordsPerThread + i) )
405+
if (finishCount.incrementAndGet == numThreads) {
406+
MultiThreadTestReceiver.haveAllThreadsFinished = true
407+
}
408+
logInfo("Finished thread " + threadId)
409+
}
410+
}
411+
executorPool.submit(runnable)
412+
})
413+
}
414+
415+
protected def onStop() {
416+
executorPool.shutdown()
417+
}
418+
}
419+
420+
object MultiThreadTestReceiver {
421+
var haveAllThreadsFinished = false
422+
}

0 commit comments

Comments
 (0)