This is an automated email from the ASF dual-hosted git repository.
zeroshade pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-go.git
The following commit(s) were added to refs/heads/main by this push:
new 2eae1935 fix(parquet/file): close file on OpenParquetFile errors
(#1053)
2eae1935 is described below
commit 2eae193511c15d73ad473057f2db3597ebb560a0
Author: Minh Vu <[email protected]>
AuthorDate: Wed Aug 5 20:03:33 2026 +0200
fix(parquet/file): close file on OpenParquetFile errors (#1053)
### Rationale for this change
`OpenParquetFile` opens a file or mmap before constructing the reader.
If initialization fails while parsing metadata, the function returned
without closing that owned source.
### What changes are included in this PR?
Require the internally opened source to implement `ReaderAt`, `Seeker`,
and `Closer`, then close it when `NewParquetReader` fails. Preserve a
simultaneous close failure with `errors.Join`.
### Are these changes tested?
Yes. A close-tracking regression test verifies that malformed input
closes the owned source, and the mmap signature cross-compiles on
Windows.
`go test ./parquet/file -run TestOpenParquetFileClosesSourceOnError`
### Are there any user-facing changes?
Invalid files still return an error, but their file descriptor or mmap
is released promptly.
---
parquet/file/file_reader.go | 17 ++++++++--
parquet/file/file_reader_mmap.go | 3 +-
parquet/file/file_reader_mmap_windows.go | 8 ++---
parquet/file/open_file_internal_test.go | 55 ++++++++++++++++++++++++++++++++
4 files changed, 73 insertions(+), 10 deletions(-)
diff --git a/parquet/file/file_reader.go b/parquet/file/file_reader.go
index 7b6df98a..5b6978ab 100644
--- a/parquet/file/file_reader.go
+++ b/parquet/file/file_reader.go
@@ -42,6 +42,11 @@ var (
errInconsistentFileMetadata = errors.New("parquet: file is smaller than
indicated metadata size")
)
+type readerAtSeekerCloser interface {
+ parquet.ReaderAtSeeker
+ io.Closer
+}
+
// Reader is the main interface for reading a parquet file
type Reader struct {
r parquet.ReaderAtSeeker
@@ -78,7 +83,7 @@ func WithMetadata(m *metadata.FileMetaData) ReadOption {
// then the default ReaderProperties will be used. The WithMetadata option can
be used to provide
// a FileMetaData object rather than reading the file metadata from the file.
func OpenParquetFile(filename string, memoryMap bool, opts ...ReadOption)
(*Reader, error) {
- var source parquet.ReaderAtSeeker
+ var source readerAtSeekerCloser
var err error
if memoryMap {
@@ -92,7 +97,15 @@ func OpenParquetFile(filename string, memoryMap bool, opts
...ReadOption) (*Read
return nil, err
}
}
- return NewParquetReader(source, opts...)
+ return newParquetReaderFromFile(source, opts...)
+}
+
+func newParquetReaderFromFile(source readerAtSeekerCloser, opts ...ReadOption)
(*Reader, error) {
+ rdr, err := NewParquetReader(source, opts...)
+ if err != nil {
+ return nil, errors.Join(err, source.Close())
+ }
+ return rdr, nil
}
// NewParquetReader returns a FileReader instance that reads a parquet file
which can be read from r.
diff --git a/parquet/file/file_reader_mmap.go b/parquet/file/file_reader_mmap.go
index 727a3879..4a4455de 100644
--- a/parquet/file/file_reader_mmap.go
+++ b/parquet/file/file_reader_mmap.go
@@ -23,11 +23,10 @@ import (
"errors"
"io"
- "github.com/apache/arrow-go/v18/parquet"
"golang.org/x/exp/mmap"
)
-func mmapOpen(filename string) (parquet.ReaderAtSeeker, error) {
+func mmapOpen(filename string) (readerAtSeekerCloser, error) {
rdr, err := mmap.Open(filename)
if err != nil {
return nil, err
diff --git a/parquet/file/file_reader_mmap_windows.go
b/parquet/file/file_reader_mmap_windows.go
index 92e9620a..5e46adbd 100644
--- a/parquet/file/file_reader_mmap_windows.go
+++ b/parquet/file/file_reader_mmap_windows.go
@@ -19,12 +19,8 @@
package file
-import (
- "errors"
+import "errors"
- "github.com/apache/arrow-go/v18/parquet"
-)
-
-func mmapOpen(_ string) (parquet.ReaderAtSeeker, error) {
+func mmapOpen(_ string) (readerAtSeekerCloser, error) {
return nil, errors.New("mmap not implemented on windows")
}
diff --git a/parquet/file/open_file_internal_test.go
b/parquet/file/open_file_internal_test.go
new file mode 100644
index 00000000..b83e5df7
--- /dev/null
+++ b/parquet/file/open_file_internal_test.go
@@ -0,0 +1,55 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+package file
+
+import (
+ "bytes"
+ "errors"
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+type closeTrackingReader struct {
+ *bytes.Reader
+ closed bool
+ closeErr error
+}
+
+func (r *closeTrackingReader) Close() error {
+ r.closed = true
+ return r.closeErr
+}
+
+func TestOpenParquetFileJoinsSourceCloseErrors(t *testing.T) {
+ closeErr := errors.New("close failed")
+ source := &closeTrackingReader{Reader:
bytes.NewReader([]byte("invalid")), closeErr: closeErr}
+ rdr, err := newParquetReaderFromFile(source)
+ require.Error(t, err)
+ require.Nil(t, rdr)
+ require.ErrorIs(t, err, closeErr)
+ require.True(t, source.closed)
+}
+
+func TestOpenParquetFileClosesSourceOnError(t *testing.T) {
+ source := &closeTrackingReader{Reader:
bytes.NewReader([]byte("invalid"))}
+ rdr, err := newParquetReaderFromFile(source)
+ require.Error(t, err)
+ require.Nil(t, rdr)
+ require.True(t, source.closed)
+}