zeroshade commented on code in PR #1506:
URL: https://github.com/apache/arrow-go/pull/1506#discussion_r4528398292


##########
parquet/file/file_writer_test.go:
##########
@@ -1674,3 +1675,97 @@ func TestPqarrowNewFileWriter_PropagatesShortWrite(t 
*testing.T) {
        require.Nil(t, writer)
        require.Contains(t, err.Error(), "short write of magic number")
 }
+
+type byteLimitSink struct {
+       buf   bytes.Buffer
+       limit int
+}
+
+var errSinkFull = errors.New("sink full")
+
+func (s *byteLimitSink) Write(p []byte) (int, error) {
+       if room := s.limit - s.buf.Len(); len(p) > room {
+               s.buf.Write(p[:max(room, 0)])
+               return max(room, 0), errSinkFull
+       }
+       return s.buf.Write(p)
+}
+
+func newPlainAndDictStringSchema(t *testing.T) *schema.GroupNode {
+       t.Helper()
+       fields := schema.FieldList{
+               schema.Must(schema.NewPrimitiveNodeLogical("plain", 
parquet.Repetitions.Optional,
+                       schema.StringLogicalType{}, parquet.Types.ByteArray, 
-1, -1)),
+               schema.Must(schema.NewPrimitiveNodeLogical("dict", 
parquet.Repetitions.Optional,
+                       schema.StringLogicalType{}, parquet.Types.ByteArray, 
-1, -1)),
+       }
+       sc, err := schema.NewGroupNode("schema", parquet.Repetitions.Required, 
fields, 0)
+       require.NoError(t, err)
+       return sc
+}
+
+func writeStringColumn(cw file.ColumnChunkWriter) error {
+       const n = 1000
+       values := make([]parquet.ByteArray, n)
+       defLevels := make([]int16, n)
+       for i := range values {
+               values[i] = parquet.ByteArray("value-" + strconv.Itoa(i%10))
+               defLevels[i] = 1
+       }
+       _, err := cw.(*file.ByteArrayColumnChunkWriter).WriteBatch(values, 
defLevels, nil)
+       return err
+}
+
+func TestWriterReleasesBuffersWhenSinkFails(t *testing.T) {
+       writeFile := func(t *testing.T, mem memory.Allocator, sink io.Writer, 
buffered bool) error {
+               props := parquet.NewWriterProperties(
+                       parquet.WithAllocator(mem),
+                       parquet.WithDictionaryDefault(true),
+                       parquet.WithDictionaryFor("plain", false),
+                       parquet.WithDataPageSize(1024),
+                       parquet.WithBatchSize(100),
+               )
+               writer, err := file.NewParquetWriterWithError(sink, 
newPlainAndDictStringSchema(t), file.WithWriterProps(props))
+               require.NoError(t, err)
+
+               if buffered {
+                       rgw, err := writer.AppendBufferedRowGroupChecked()
+                       require.NoError(t, err)
+                       for i := 0; i < rgw.NumColumns(); i++ {
+                               cw, err := rgw.Column(i)
+                               require.NoError(t, err)
+                               require.NoError(t, writeStringColumn(cw))
+                       }
+               } else {
+                       rgw, err := writer.AppendRowGroupChecked()
+                       require.NoError(t, err)
+                       for i := 0; i < rgw.NumColumns(); i++ {
+                               cw, err := rgw.NextColumn()
+                               if err == nil {
+                                       err = writeStringColumn(cw)
+                               }
+                               if err != nil {
+                                       return errors.Join(err, writer.Close())
+                               }
+                       }
+               }
+               return writer.Close()
+       }
+
+       for _, buffered := range []bool{false, true} {
+               t.Run(fmt.Sprintf("buffered=%t", buffered), func(t *testing.T) {
+                       var full bytes.Buffer
+                       require.NoError(t, writeFile(t, 
memory.DefaultAllocator, &full, buffered))
+
+                       for limit := 4; limit < full.Len(); limit++ {

Review Comment:
   Same here: under `-race` the `parquet/file` package takes 24.5 s, mostly 
this test. Sampling offsets, as suggested on the pqarrow test, would cut that 
down.



##########
parquet/pqarrow/file_writer_test.go:
##########
@@ -352,3 +356,82 @@ func TestWriteOnClosedFileWriter(t *testing.T) {
        require.ErrorContains(t, writer.WriteColumnChunked(nil, 0, 0), "already 
closed")
        require.ErrorContains(t, writer.WriteColumnData(nil), "already closed")
 }
+
+type byteLimitSink struct {
+       buf    bytes.Buffer
+       limit  int
+       closed bool
+}
+
+func (s *byteLimitSink) Close() error {
+       s.closed = true
+       return nil
+}
+
+var errSinkFull = errors.New("sink full")
+
+func (s *byteLimitSink) Write(p []byte) (int, error) {
+       if room := s.limit - s.buf.Len(); len(p) > room {
+               s.buf.Write(p[:max(room, 0)])
+               return max(room, 0), errSinkFull
+       }
+       return s.buf.Write(p)
+}
+
+func TestFileWriterCloseReleasesBuffersWhenSinkFails(t *testing.T) {
+       schema := arrow.NewSchema([]arrow.Field{
+               {Name: "num", Nullable: true, Type: arrow.PrimitiveTypes.Int8},
+               {Name: "dict", Nullable: true, Type: 
&arrow.DictionaryType{IndexType: arrow.PrimitiveTypes.Int32, ValueType: 
arrow.BinaryTypes.String}},
+               {Name: "str", Nullable: true, Type: arrow.BinaryTypes.String},
+       }, nil)
+
+       buildRecord := func(mem memory.Allocator) arrow.RecordBatch {
+               bldr := array.NewRecordBuilder(mem, schema)
+               defer bldr.Release()
+               num := bldr.Field(0).(*array.Int8Builder)
+               dict := bldr.Field(1).(*array.BinaryDictionaryBuilder)
+               str := bldr.Field(2).(*array.StringBuilder)
+               for i := 0; i < 1000; i++ {
+                       require.NoError(t, 
dict.AppendString("value-"+strconv.Itoa(i%10)))
+                       str.Append("row-" + strconv.Itoa(i))
+                       num.Append(int8(i))
+               }
+               return bldr.NewRecordBatch()
+       }
+
+       writeFile := func(t *testing.T, mem memory.Allocator, sink io.Writer, 
buffered bool) error {
+               rec := buildRecord(mem)
+               defer rec.Release()
+
+               writer, err := pqarrow.NewFileWriter(schema, sink,
+                       parquet.NewWriterProperties(parquet.WithAllocator(mem), 
parquet.WithDictionaryFor("str", false)),
+                       
pqarrow.NewArrowWriterProperties(pqarrow.WithAllocator(mem), 
pqarrow.WithStoreSchema()))
+               require.NoError(t, err)
+
+               if buffered {
+                       err = writer.WriteBuffered(rec)
+               } else {
+                       err = writer.Write(rec)
+               }
+               return errors.Join(err, writer.Close())
+       }
+
+       for _, buffered := range []bool{false, true} {
+               t.Run(fmt.Sprintf("buffered=%t", buffered), func(t *testing.T) {
+                       var full bytes.Buffer
+                       require.NoError(t, writeFile(t, 
memory.DefaultAllocator, &full, buffered))
+
+                       for limit := 4; limit < full.Len(); limit++ {

Review Comment:
   Failing at every byte offset is slow under `-race`, which CI uses on macOS. 
Those jobs are already close to their timeout (see the comment at 
`ci/scripts/test.sh:149`). Locally on arm64 this test alone takes 113.8 s with 
`-race` (51 s unbuffered + 62 s buffered).
   
   Could this test a sample of offsets instead? For example every ~37th byte, 
plus the bytes just before and after each page and column chunk boundary, taken 
from the successful file's metadata. That covers the same failure points in a 
fraction of the time. Writing fewer rows would also help.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to