Skip to content

Commit 50a608d

Browse files
committed
Fixed bugs and added support for writeObject
1 parent cafd505 commit 50a608d

File tree

2 files changed

+133
-8
lines changed

2 files changed

+133
-8
lines changed

core/src/main/scala/org/apache/spark/serializer/SerializationDebugger.scala

Lines changed: 60 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.spark.serializer
1919

20-
import java.io.{NotSerializableException, ObjectOutput, ObjectStreamClass, ObjectStreamField}
20+
import java.io._
2121
import java.lang.reflect.{Field, Method}
2222
import java.security.AccessController
2323

@@ -145,17 +145,25 @@ private[spark] object SerializationDebugger extends Logging {
145145
// An object contains multiple slots in serialization.
146146
// Get the slots and visit fields in all of them.
147147
val (finalObj, desc) = findObjectAndDescriptor(o)
148+
149+
if (!finalObj.eq(o)) {
150+
return visit(finalObj, s"writeReplace data (class: ${finalObj.getClass.getName})" :: stack)
151+
}
152+
148153
val slotDescs = desc.getSlotDescs
149154
var i = 0
150155
while (i < slotDescs.length) {
151156
val slotDesc = slotDescs(i)
152157
if (slotDesc.hasWriteObjectMethod) {
153-
// TODO: Handle classes that specify writeObject method.
158+
val childStack = visitSerializableWithWriteObjectMethod(finalObj, slotDesc, stack)
159+
if (childStack.nonEmpty) {
160+
return childStack
161+
}
154162
} else {
155163
val fields: Array[ObjectStreamField] = slotDesc.getFields
156164
val objFieldValues: Array[Object] = new Array[Object](slotDesc.getNumObjFields)
157165
val numPrims = fields.length - objFieldValues.length
158-
desc.getObjFieldValues(finalObj, objFieldValues)
166+
slotDesc.getObjFieldValues(finalObj, objFieldValues)
159167

160168
var j = 0
161169
while (j < objFieldValues.length) {
@@ -169,12 +177,39 @@ private[spark] object SerializationDebugger extends Logging {
169177
}
170178
j += 1
171179
}
172-
173180
}
174181
i += 1
175182
}
176183
return List.empty
177184
}
185+
186+
private def visitSerializableWithWriteObjectMethod(
187+
o: Object, slotDesc: ObjectStreamClass, stack: List[String]): List[String] = {
188+
println(">>> processing serializable with writeObject" + o)
189+
val innerObjectsCatcher = new ListObjectOutputStream
190+
var notSerializableFound = false
191+
try {
192+
innerObjectsCatcher.writeObject(o)
193+
} catch {
194+
case io: IOException =>
195+
notSerializableFound = true
196+
}
197+
if (notSerializableFound) {
198+
val innerObjects = innerObjectsCatcher.outputArray
199+
var k = 0
200+
while (k < innerObjects.length) {
201+
val elem = s"writeObject data (class: ${slotDesc.getName})"
202+
val childStack = visit(innerObjects(k), elem :: stack)
203+
if (childStack.nonEmpty) {
204+
return childStack
205+
}
206+
k += 1
207+
}
208+
} else {
209+
visited ++= innerObjectsCatcher.outputArray
210+
}
211+
return List.empty
212+
}
178213
}
179214

180215
/**
@@ -220,6 +255,27 @@ private[spark] object SerializationDebugger extends Logging {
220255
override def writeByte(i: Int): Unit = {}
221256
}
222257

258+
/** An output stream that emulates /dev/null */
259+
private class NullOutputStream extends OutputStream {
260+
override def write(b: Int) { }
261+
}
262+
263+
/**
264+
* A dummy [[ObjectOutputStream]] that saves the list of objects written to it and returns
265+
* them through `outputArray`.
266+
*/
267+
private class ListObjectOutputStream extends ObjectOutputStream(new NullOutputStream) {
268+
private val output = new mutable.ArrayBuffer[Any]
269+
this.enableReplaceObject(true)
270+
271+
def outputArray: Array[Any] = output.toArray
272+
273+
override def replaceObject(obj: Object): Object = {
274+
output += obj
275+
obj
276+
}
277+
}
278+
223279
/** An implicit class that allows us to call private methods of ObjectStreamClass. */
224280
implicit class ObjectStreamClassMethods(val desc: ObjectStreamClass) extends AnyVal {
225281
def getSlotDescs: Array[ObjectStreamClass] = {

core/src/test/scala/org/apache/spark/serializer/SerializationDebuggerSuite.scala

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717

1818
package org.apache.spark.serializer
1919

20-
import java.io.{ObjectOutput, ObjectInput}
20+
import java.io.{IOException, ObjectOutputStream, ObjectOutput, ObjectInput}
2121

2222
import org.scalatest.BeforeAndAfterEach
2323

@@ -98,14 +98,59 @@ class SerializationDebuggerSuite extends SparkFunSuite with BeforeAndAfterEach {
9898
}
9999

100100
test("externalizable class writing out not serializable object") {
101-
val s = find(new ExternalizableClass)
101+
val s = find(new ExternalizableClass(new SerializableClass2(new NotSerializable)))
102102
assert(s.size === 5)
103103
assert(s(0).contains("NotSerializable"))
104104
assert(s(1).contains("objectField"))
105105
assert(s(2).contains("SerializableClass2"))
106106
assert(s(3).contains("writeExternal"))
107107
assert(s(4).contains("ExternalizableClass"))
108108
}
109+
110+
test("externalizable class writing out serializable objects") {
111+
assert(find(new ExternalizableClass(new SerializableClass1)).isEmpty)
112+
}
113+
114+
test("object containing writeReplace() which returns not serializable object") {
115+
val s = find(new SerializableClassWithWriteReplace(new NotSerializable))
116+
println("-----\n" + s.zipWithIndex.mkString("\n") + "\n----")
117+
assert(s.size === 3)
118+
assert(s(0).contains("NotSerializable"))
119+
assert(s(1).contains("writeReplace"))
120+
assert(s(2).contains("SerializableClassWithWriteReplace"))
121+
}
122+
123+
test("object containing writeObject() and not serializable field") {
124+
val s = find(new SerializableClassWithWriteObject(new NotSerializable))
125+
println("-----\n" + s.zipWithIndex.mkString("\n") + "\n----")
126+
assert(s.size === 3)
127+
assert(s(0).contains("NotSerializable"))
128+
assert(s(1).contains("writeObject data"))
129+
assert(s(2).contains("SerializableClassWithWriteObject"))
130+
}
131+
132+
test("object containing writeObject() and serializable field") {
133+
assert(find(new SerializableClassWithWriteObject(new SerializableClass1)).isEmpty)
134+
}
135+
136+
137+
test("object of serializable subclass with more fields than superclass (SPARK-7180)") {
138+
// This should not throw ArrayOutOfBoundsException
139+
find(new SerializableSubclass(new SerializableClass1))
140+
}
141+
142+
test("crazy nested objects") {
143+
val s = find(
144+
new SerializableClassWithWriteReplace(
145+
new ExternalizableClass(
146+
new SerializableSubclass(
147+
new SerializableArray(
148+
Array(new SerializableClass1, new SerializableClass2(new NotSerializable))
149+
))))
150+
)
151+
assert(s.nonEmpty)
152+
assert(s.head.contains("NotSerializable"))
153+
}
109154
}
110155

111156

@@ -118,10 +163,34 @@ class SerializableClass2(val objectField: Object) extends Serializable
118163
class SerializableArray(val arrayField: Array[Object]) extends Serializable
119164

120165

121-
class ExternalizableClass extends java.io.Externalizable {
166+
class SerializableSubclass(val objectField: Object) extends SerializableClass1
167+
168+
169+
class SerializableClassWithWriteObject(val objectField: Object) extends Serializable {
170+
val serializableObjectField = new SerializableClass1
171+
172+
@throws(classOf[IOException])
173+
private def writeObject(oos: ObjectOutputStream): Unit = {
174+
oos.defaultWriteObject()
175+
}
176+
}
177+
178+
179+
class SerializableClassWithWriteReplace(@transient replacementFieldObject: Object)
180+
extends Serializable {
181+
private def writeReplace(): Object = {
182+
replacementFieldObject
183+
}
184+
}
185+
186+
187+
class ExternalizableClass(objectField: Object) extends java.io.Externalizable {
188+
val serializableObjectField = new SerializableClass1
189+
122190
override def writeExternal(out: ObjectOutput): Unit = {
123191
out.writeInt(1)
124-
out.writeObject(new SerializableClass2(new NotSerializable))
192+
out.writeObject(serializableObjectField)
193+
out.writeObject(objectField)
125194
}
126195

127196
override def readExternal(in: ObjectInput): Unit = {}

0 commit comments

Comments
 (0)