SequenceFile's Reader.decompressorPool or Writer.decompressorPool gets into an
inconsistent state when calling close() more than once
-------------------------------------------------------------------------------------------------------------------------------------
Key: HADOOP-3821
URL: https://issues.apache.org/jira/browse/HADOOP-3821
Project: Hadoop Core
Issue Type: Bug
Components: io
Affects Versions: 0.17.1, 0.17.0, 0.18.0, 0.19.0
Reporter: Peter Voss
SequenceFile.Reader uses a decompressorPool to reuse Decompressor instances.
The Reader obtains such an instance from the pool on object creation and
returns it back to the pool it when {{close()}} is called.
SequenceFile.Reader implements the {{java.io.Closable}} interface and it's spec
on the {{close()}} method says:
{quote}
Closes this stream and releases any system resources associated
with it. If the stream is already closed then invoking this
method has no effect.
{quote}
This spec is violated by the Reader implementation, because calling {{close()}}
multiple times has really bad implications.
When you call {{close()}} twice, one and the same Decompressor instances will
be returned to the pool two times and the pool would now maintain duplicated
references to the same Decompressor instances. When other Readers now request
instances from the pool it might happen that two Readers get the same
Decompressor instance.
The correct behavior would be to just ignore a second call to {{close()}}.
The exact same issue applies to the SequenceFile.Writer as well.
We were having big trouble with this, because we were observing sporadic
exceptions from merge operations. The strange thing was that executing the same
merge again usually succeeded. But sometimes it took multiple attempts to
complete a merge successfully. It was very hard to debug that the root cause
was some duplicated Decompressor references in the decompressorPool.
Exceptions that we observed in production looked like this (we were using
hadoop 0.17.0):
{noformat}
java.io.IOException: unknown compression method
at
org.apache.hadoop.io.compress.zlib.BuiltInZlibInflater.decompress(BuiltInZlibInflater.java:47)
at
org.apache.hadoop.io.compress.DecompressorStream.decompress(DecompressorStream.java:80)
at
org.apache.hadoop.io.compress.DecompressorStream.read(DecompressorStream.java:74)
at java.io.DataInputStream.readFully(DataInputStream.java:178)
at org.apache.hadoop.io.DataOutputBuffer$Buffer.write(DataOutputBuffer.java:56)
at org.apache.hadoop.io.DataOutputBuffer.write(DataOutputBuffer.java:90)
at org.apache.hadoop.io.SequenceFile$Reader.nextRawKey(SequenceFile.java:1995)
at
org.apache.hadoop.io.SequenceFile$Sorter$SegmentDescriptor.nextRawKey(SequenceFile.java:3002)
at
org.apache.hadoop.io.SequenceFile$Sorter$MergeQueue.next(SequenceFile.java:2760)
at org.apache.hadoop.io.SequenceFile$Sorter.writeFile(SequenceFile.java:2625)
at org.apache.hadoop.io.SequenceFile$Sorter.merge(SequenceFile.java:2644)
{noformat}
or
{noformat}
java.io.IOException: zero length keys not allowed
at
org.apache.hadoop.io.SequenceFile$BlockCompressWriter.appendRaw(SequenceFile.java:1340)
at org.apache.hadoop.io.SequenceFile$Sorter.writeFile(SequenceFile.java:2626)
at org.apache.hadoop.io.SequenceFile$Sorter.merge(SequenceFile.java:2644)
{noformat}
The following snippet reproduces the problem:
{code:java}
public void testCodecPool() throws IOException {
Configuration conf = new Configuration();
LocalFileSystem fs = new LocalFileSystem();
fs.setConf(conf);
fs.getRawFileSystem().setConf(conf);
// create a sequence file
Path path = new Path("target/seqFile");
SequenceFile.Writer writer = SequenceFile.createWriter(fs, conf, path,
Text.class, NullWritable.class, CompressionType.BLOCK);
writer.append(new Text("key1"), NullWritable.get());
writer.append(new Text("key2"), NullWritable.get());
writer.close();
// Create a reader which uses 4 BuiltInZLibInflater instances
SequenceFile.Reader reader = new SequenceFile.Reader(fs, path, conf);
// Returns the 4 BuiltInZLibInflater instances to the CodecPool
reader.close();
// The second close erroneously returns the same 4 BuiltInZLibInflater
instances to the CodecPool again
reader.close();
// The first reader gets 4 BuiltInZLibInflater instances from the
CodecPool
SequenceFile.Reader reader1 = new SequenceFile.Reader(fs, path, conf);
// read first value from reader1
Text text = new Text();
reader1.next(text);
assertEquals("key1", text.toString());
// The second reader gets the same 4 BuiltInZLibInflater instances from
the CodePool as reader1
SequenceFile.Reader reader2 = new SequenceFile.Reader(fs, path, conf);
// read first value from reader2
reader2.next(text);
assertEquals("key1", text.toString());
// read second value from reader1
reader1.next(text);
assertEquals("key2", text.toString());
// read second value from reader2 (this throws an exception)
reader2.next(text);
assertEquals("key2", text.toString());
assertFalse(reader1.next(text));
assertFalse(reader2.next(text));
}
{code}
It fails with the exception:
{noformat}
java.io.EOFException
at java.io.DataInputStream.readByte(DataInputStream.java:243)
at org.apache.hadoop.io.WritableUtils.readVLong(WritableUtils.java:324)
at org.apache.hadoop.io.WritableUtils.readVInt(WritableUtils.java:345)
at org.apache.hadoop.io.SequenceFile$Reader.next(SequenceFile.java:1835)
at CodecPoolTest.testCodecPool(CodecPoolTest.java:56)
{noformat}
But this is just a very simple test that shows the problem. Much more weired
things can happen when running in a complex production environment. Esp. heavy
concurrency makes the behavior much more exciting. ;-)
--
This message is automatically generated by JIRA.
-
You can reply to this email to add a comment to the issue online.