Skip to content
Closed
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
package org.apache.spark.broadcast

import java.io._
import java.lang.ref.WeakReference
import java.nio.ByteBuffer
import java.util.zip.Adler32

Expand Down Expand Up @@ -61,9 +62,11 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long)
* Value of the broadcast object on executors. This is reconstructed by [[readBroadcastBlock]],
* which builds this value by reading blocks from the driver and/or other executors.
*
* On the driver, if the value is required, it is read lazily from the block manager.
* On the driver, if the value is required, it is read lazily from the block manager. We hold
* a weak reference so that it can be garbage collected if required, as we can always reconstruct
* in the future.
*/
@transient private lazy val _value: T = readBroadcastBlock()
@transient private var _value: WeakReference[T] = _

/** The compression codec to use, or None if compression is disabled */
@transient private var compressionCodec: Option[CompressionCodec] = _
Expand Down Expand Up @@ -93,7 +96,14 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long)
private var checksums: Array[Int] = _

override protected def getValue() = {
_value
val memoized: T = if (_value == null) null.asInstanceOf[T] else _value.get

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose there is a race condition here, in that several threads could end up simultaneously setting the reference. It won't be incorrect as the data ought to be the same. I am not sure of the access pattern for this object; maybe it's always single-threaded. But if lots are reading, you can imagine them all causing a call to readBroadcastBlock() simultaneously.

Introducing another object to lock on is safe and not too much extra legwork. Might be worth it.

Isn't WeakReference cleared on any GC? would SoftReference be better to hold out until memory is exhausted? to avoid re-reading. There's a tradeoff there.

Good idea, just surprisingly full of possible gotchas.

Nit: isn't val memoized = if (_value == null) null else _value.get sufficient?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I'll make it synchronized, so it only loads one at a time.

Re: WeakReference, sure, I can change it to SoftReference. That'll be closer to the original behavior, and should still give the improvement we want.

When I try with .asInstanceOf[T] it fails to compile with:

[error] /Users/bkrieger/Documents/git/spark/core/src/main/scala/org/apache/spark/broadcast/TorrentBroadcast.scala:98: type mismatch;
[error]  found   : Null(null)
[error]  required: T
[error]     val memoized: T = if (_value == null) null else _value.get
[error]                                           ^
[info] Null(null) <: T?
[info] false
[error] one error found

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, weird, I thought it would work based on a little local example, but yeah leave the cast in of course.

if (memoized != null) {
memoized
} else {
val newlyRead = readBroadcastBlock()
_value = new WeakReference[T](newlyRead)
newlyRead
}
}

private def calcChecksum(block: ByteBuffer): Int = {
Expand Down