-
Notifications
You must be signed in to change notification settings - Fork 29.3k
[SPARK-3137][Core]Replace the global TorrentBroadcast lock with fine grained KeyLock #25612
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,6 +17,7 @@ | |
|
|
||
| package org.apache.spark.broadcast | ||
|
|
||
| import java.util.Collections | ||
| import java.util.concurrent.atomic.AtomicLong | ||
|
|
||
| import scala.reflect.ClassTag | ||
|
|
@@ -56,7 +57,10 @@ private[spark] class BroadcastManager( | |
| private val nextBroadcastId = new AtomicLong(0) | ||
|
|
||
| private[broadcast] val cachedValues = { | ||
| new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK) | ||
| Collections.synchronizedMap( | ||
| new ReferenceMap(AbstractReferenceMap.HARD, AbstractReferenceMap.WEAK) | ||
| .asInstanceOf[java.util.Map[Any, Any]] | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one now needs protection as the global lock is removed. Casting it because Scala compiler cannot figure it out.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: would it make compiler happy and be nicer if the val was typed (val cachedValues: JMap[Any, Any]) instead of doing
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No. The compiler doesn't like Java classes :P
srowen marked this conversation as resolved.
|
||
| ) | ||
| } | ||
|
|
||
| def newBroadcast[T: ClassTag](value_ : T, isLocal: Boolean): Broadcast[T] = { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,6 +20,7 @@ package org.apache.spark.broadcast | |
| import java.io._ | ||
| import java.lang.ref.SoftReference | ||
| import java.nio.ByteBuffer | ||
| import java.util.concurrent.ConcurrentHashMap | ||
| import java.util.zip.Adler32 | ||
|
|
||
| import scala.collection.JavaConverters._ | ||
|
|
@@ -167,7 +168,7 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long) | |
| bm.getLocalBytes(pieceId) match { | ||
| case Some(block) => | ||
| blocks(pid) = block | ||
| releaseLock(pieceId) | ||
| releaseBlockManagerLock(pieceId) | ||
| case None => | ||
| bm.getRemoteBytes(pieceId) match { | ||
| case Some(b) => | ||
|
|
@@ -215,8 +216,10 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long) | |
| } | ||
|
|
||
| private def readBroadcastBlock(): T = Utils.tryOrIOException { | ||
| val broadcastCache = SparkEnv.get.broadcastManager.cachedValues | ||
| broadcastCache.synchronized { | ||
| TorrentBroadcast.withTorrentBroadcastLock(broadcastId) { | ||
| // As we just lock based on `broadcastId`, whenever using `broadcastCache`, we should just | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: just -> only |
||
| // touch `broadcastId`. | ||
| val broadcastCache = SparkEnv.get.broadcastManager.cachedValues | ||
|
|
||
| Option(broadcastCache.get(broadcastId)).map(_.asInstanceOf[T]).getOrElse { | ||
| setConf(SparkEnv.get.conf) | ||
|
|
@@ -225,7 +228,7 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long) | |
| case Some(blockResult) => | ||
| if (blockResult.data.hasNext) { | ||
| val x = blockResult.data.next().asInstanceOf[T] | ||
| releaseLock(broadcastId) | ||
| releaseBlockManagerLock(broadcastId) | ||
|
|
||
| if (x != null) { | ||
| broadcastCache.put(broadcastId, x) | ||
|
|
@@ -270,7 +273,7 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long) | |
| * If running in a task, register the given block's locks for release upon task completion. | ||
| * Otherwise, if not running in a task then immediately release the lock. | ||
| */ | ||
| private def releaseLock(blockId: BlockId): Unit = { | ||
| private def releaseBlockManagerLock(blockId: BlockId): Unit = { | ||
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rename this method to avoid confusing. |
||
| val blockManager = SparkEnv.get.blockManager | ||
| Option(TaskContext.get()) match { | ||
| case Some(taskContext) => | ||
|
|
@@ -290,6 +293,42 @@ private[spark] class TorrentBroadcast[T: ClassTag](obj: T, id: Long) | |
|
|
||
| private object TorrentBroadcast extends Logging { | ||
|
|
||
| /** Locks to ensure there is only one thread fetching the same [[TorrentBroadcast]] block. */ | ||
| private val torrentBroadcastLock = new ConcurrentHashMap[BroadcastBlockId, AnyRef]() | ||
|
|
||
| /** Acquire a lock for fetching a [[TorrentBroadcast]] block. */ | ||
| private def acquireTorrentBroadcastLock(broadcastId: BroadcastBlockId): Unit = { | ||
| while (true) { | ||
| val lock = torrentBroadcastLock.putIfAbsent(broadcastId, new Object) | ||
| if (lock == null) return | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this happen?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yep when no such key in
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Right OK, but then it isn't locking on anything the first time, how does this work?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry I get it now. The Object isn't really the lock. It's the presence of absence of the Object. The Object is used to queue up waiting callers. OK but this seems simpler if you
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
The
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Isn't this all there is to it, more or less? The downside I suppose is that you have a map of locks that keeps growing, but how many broadcast IDs would there be? vs a relatively small lock object for each.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The number of broadcast IDs is significant enough for a long running application. Even if the user doesn't use broadcast, we will still use it internally, for example, task binary is a broadcast object, a Spark SQL query may use broadcast join...
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, makes sense. Well, could still make sense to use a |
||
| lock.synchronized { | ||
| while (torrentBroadcastLock.get(broadcastId) eq lock) { | ||
|
srowen marked this conversation as resolved.
Outdated
|
||
| lock.wait() | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about just using a Java
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Using a
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, just a ReentrantLock. I think you don't need to deal with spurious wakeups then either
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I just tried
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why not just stripe over a fixed number of Reentrant locks? This should reduce contention enough (and potential collissions can be controlled with numLocks) is easy to understand and doesn't reimplement its own lock queues. It's also fully interruptible, while in the current implementation the threads that are queueing for the lock are not interruptible. |
||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** Release the lock for a [[TorrentBroadcast]] block. */ | ||
| private def releaseTorrentBroadcastLock(broadcastId: BroadcastBlockId): Unit = { | ||
| val lock = torrentBroadcastLock.remove(broadcastId) | ||
| if (lock != null) { | ||
| lock.synchronized { | ||
| lock.notifyAll() | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /** Acquire and hold the lock to access `broadcastId` when calling `func`. */ | ||
| private def withTorrentBroadcastLock[T](broadcastId: BroadcastBlockId)(func: => T): T = { | ||
| acquireTorrentBroadcastLock(broadcastId) | ||
| try { | ||
| func | ||
| } finally { | ||
| releaseTorrentBroadcastLock(broadcastId) | ||
| } | ||
| } | ||
|
|
||
| def blockifyObject[T: ClassTag]( | ||
| obj: T, | ||
| blockSize: Int, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You don't need the braces here, but I wouldn't bother unless you're changing this file again