You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by bk...@apache.org on 2023/11/27 15:45:04 UTC

(arrow) branch main updated: GH-38795: [Go] Fix race GetToTimeFunc for Timestamp (#38797)

This is an automated email from the ASF dual-hosted git repository.

bkietz pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 62e1e9ad76 GH-38795: [Go] Fix race GetToTimeFunc for Timestamp (#38797)
62e1e9ad76 is described below

commit 62e1e9ad76493729af6d605b7c0fcb31ebcbda5a
Author: Matt Topol <zo...@gmail.com>
AuthorDate: Mon Nov 27 10:44:53 2023 -0500

    GH-38795: [Go] Fix race GetToTimeFunc for Timestamp (#38797)
    
    
    
    ### Rationale for this change
    Adding RWMutex to protect `loc` in `TimestampType` and fix the race condition.
    
    ### Are these changes tested?
    Yes, a unit test is added which is covered by the CI which runs with `-race`.
    
    ### Are there any user-facing changes?
    Copying `TimestampType` will now be problematic and linters will show it as an error. In theory this shouldn't be a problem as most uses of TimestampType should be utilizing pointers to it rather than the value directly.
    
    * Closes: #38795
    
    Lead-authored-by: Matt Topol <zo...@gmail.com>
    Co-authored-by: Benjamin Kietzman <be...@gmail.com>
    Co-authored-by: Ben Harkins <60...@users.noreply.github.com>
    Signed-off-by: Benjamin Kietzman <be...@gmail.com>
---
 go/arrow/compare_test.go             | 53 ++++++++++++++++++------------------
 go/arrow/datatype_fixedwidth.go      | 16 ++++++++++-
 go/arrow/datatype_fixedwidth_test.go | 25 +++++++++++++++++
 3 files changed, 66 insertions(+), 28 deletions(-)

diff --git a/go/arrow/compare_test.go b/go/arrow/compare_test.go
index 170fc2d852..62e30e634e 100644
--- a/go/arrow/compare_test.go
+++ b/go/arrow/compare_test.go
@@ -116,13 +116,13 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			false, true,
 		},
@@ -131,13 +131,13 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			false, false,
 		},
@@ -146,13 +146,13 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f0", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f0": []int{0}},
+				index: map[string][]int{"f0": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			false, false,
 		},
@@ -161,14 +161,14 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			false, true,
 		},
@@ -177,14 +177,14 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			false, false,
 		},
@@ -193,13 +193,13 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f2", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f2": []int{0}},
+				index: map[string][]int{"f2": {0}},
 			},
 			false, false,
 		},
@@ -209,14 +209,14 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			true, false,
 		},
@@ -226,14 +226,14 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			true, false,
 		},
@@ -243,7 +243,7 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 				meta:  MetadataFrom(map[string]string{"k1": "v1", "k2": "v2"}),
 			},
 			&StructType{
@@ -251,7 +251,7 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 				meta:  MetadataFrom(map[string]string{"k2": "v2", "k1": "v1"}),
 			},
 			true, true,
@@ -261,14 +261,14 @@ func TestTypeEqual(t *testing.T) {
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 				meta:  MetadataFrom(map[string]string{"k1": "v1"}),
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0}},
+				index: map[string][]int{"f1": {0}},
 				meta:  MetadataFrom(map[string]string{"k1": "v2"}),
 			},
 			true, false,
@@ -279,14 +279,14 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true, Metadata: MetadataFrom(map[string]string{"k1": "v1"})},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true, Metadata: MetadataFrom(map[string]string{"k1": "v2"})},
 					{Name: "f2", Type: PrimitiveTypes.Float32, Nullable: false},
 				},
-				index: map[string][]int{"f1": []int{0}, "f2": []int{1}},
+				index: map[string][]int{"f1": {0}, "f2": {1}},
 			},
 			false, true,
 		},
@@ -296,14 +296,14 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0, 1}},
+				index: map[string][]int{"f1": {0, 1}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0, 1}},
+				index: map[string][]int{"f1": {0, 1}},
 			},
 			true, true,
 		},
@@ -313,14 +313,14 @@ func TestTypeEqual(t *testing.T) {
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0, 1}},
+				index: map[string][]int{"f1": {0, 1}},
 			},
 			&StructType{
 				fields: []Field{
 					{Name: "f1", Type: PrimitiveTypes.Uint16, Nullable: true},
 					{Name: "f1", Type: PrimitiveTypes.Uint32, Nullable: true},
 				},
-				index: map[string][]int{"f1": []int{0, 1}},
+				index: map[string][]int{"f1": {0, 1}},
 			},
 			false, true,
 		},
@@ -343,7 +343,6 @@ func TestTypeEqual(t *testing.T) {
 			MapOf(BinaryTypes.String, &TimestampType{
 				Unit:     0,
 				TimeZone: "UTC",
-				loc:      nil,
 			}),
 			true, false,
 		},
diff --git a/go/arrow/datatype_fixedwidth.go b/go/arrow/datatype_fixedwidth.go
index bcbc8ef6ae..1a3074e59e 100644
--- a/go/arrow/datatype_fixedwidth.go
+++ b/go/arrow/datatype_fixedwidth.go
@@ -19,6 +19,7 @@ package arrow
 import (
 	"fmt"
 	"strconv"
+	"sync"
 	"time"
 
 	"github.com/apache/arrow/go/v15/internal/json"
@@ -354,6 +355,7 @@ type TimestampType struct {
 	TimeZone string
 
 	loc *time.Location
+	mx  sync.RWMutex
 }
 
 func (*TimestampType) ID() Type     { return TIMESTAMP }
@@ -386,6 +388,8 @@ func (t *TimestampType) TimeUnit() TimeUnit { return t.Unit }
 // This should be called if you change the value of the TimeZone after having
 // potentially called GetZone.
 func (t *TimestampType) ClearCachedLocation() {
+	t.mx.Lock()
+	defer t.mx.Unlock()
 	t.loc = nil
 }
 
@@ -398,10 +402,20 @@ func (t *TimestampType) ClearCachedLocation() {
 // so if you change the value of TimeZone after calling this, make sure to call
 // ClearCachedLocation.
 func (t *TimestampType) GetZone() (*time.Location, error) {
+	t.mx.RLock()
 	if t.loc != nil {
+		defer t.mx.RUnlock()
 		return t.loc, nil
 	}
 
+	t.mx.RUnlock()
+	t.mx.Lock()
+	defer t.mx.Unlock()
+	// in case GetZone() was called in between releasing the read lock and
+	// getting the write lock
+	if t.loc != nil {
+		return t.loc, nil
+	}
 	// the TimeZone string is allowed to be either a valid tzdata string
 	// such as "America/New_York" or an absolute offset of the form -XX:XX
 	// or +XX:XX
@@ -415,7 +429,7 @@ func (t *TimestampType) GetZone() (*time.Location, error) {
 
 	if loc, err := time.LoadLocation(t.TimeZone); err == nil {
 		t.loc = loc
-		return t.loc, err
+		return loc, err
 	}
 
 	// at this point we know that the timezone isn't empty, and didn't match
diff --git a/go/arrow/datatype_fixedwidth_test.go b/go/arrow/datatype_fixedwidth_test.go
index 918572d40b..b3cbb465f3 100644
--- a/go/arrow/datatype_fixedwidth_test.go
+++ b/go/arrow/datatype_fixedwidth_test.go
@@ -17,6 +17,7 @@
 package arrow_test
 
 import (
+	"sync"
 	"testing"
 	"time"
 
@@ -180,6 +181,30 @@ func TestTimestampType_GetToTimeFunc(t *testing.T) {
 	assert.Equal(t, "2345-12-29T19:00:00-05:00", toTimeNY(ts).Format(time.RFC3339))
 }
 
+// Test race condition from GH-38795
+func TestGetToTimeFuncRace(t *testing.T) {
+	var (
+		wg         sync.WaitGroup
+		w          = make(chan bool)
+		routineNum = 10
+	)
+
+	wg.Add(routineNum)
+	for i := 0; i < routineNum; i++ {
+		go func() {
+			defer wg.Done()
+
+			<-w
+
+			_, _ = arrow.FixedWidthTypes.Timestamp_s.(*arrow.TimestampType).GetToTimeFunc()
+		}()
+	}
+
+	close(w)
+
+	wg.Wait()
+}
+
 func TestTime32Type(t *testing.T) {
 	for _, tc := range []struct {
 		unit arrow.TimeUnit