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

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


The following commit(s) were added to refs/heads/main by this push:
     new 697295af4 fix: fix go thread safe Pool race condition and remove 
python lazy_import (#4027)
697295af4 is described below

commit 697295af47c121979c7d6910f32ff0cff3b72651
Author: Shawn Yang <[email protected]>
AuthorDate: Tue Sep 8 00:40:42 2026 +0800

    fix: fix go thread safe Pool race condition and remove python lazy_import 
(#4027)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    
Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence or equivalent persisted links of the
    final clean AI review results from both fresh reviewers described in
    `AI_POLICY.md`, the Fory-guided reviewer and the independent general
    reviewer, on the current PR diff or current HEAD after the latest code
    changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .agents/languages/go.md                       |   9 +
 ci/run_ci.sh                                  |   2 +-
 ci/tasks/go.py                                |   2 +-
 docs/object-serialization/go/configuration.md |   2 +-
 docs/object-serialization/go/thread-safety.md |  75 ++++----
 go/fory/threadsafe/fory.go                    | 128 +++++++++++--
 go/fory/threadsafe/fory_test.go               | 249 ++++++++++++++++++++++++++
 python/pyfory/format/tests/test_vectorized.py |   5 +-
 python/pyfory/tests/record.py                 |   7 +-
 python/pyfory/tests/test_buffer.py            |   6 +-
 python/pyfory/tests/test_cross_language.py    |   9 +-
 python/pyfory/tests/test_serializer.py        |   4 +-
 python/pyfory/utils.py                        |  63 -------
 13 files changed, 423 insertions(+), 138 deletions(-)

diff --git a/.agents/languages/go.md b/.agents/languages/go.md
index 5192fc3e7..ea550e49c 100644
--- a/.agents/languages/go.md
+++ b/.agents/languages/go.md
@@ -7,6 +7,15 @@ Load this file when changing `go/fory/` or Go xlang behavior.
 - Run Go commands from within `go/fory/`.
 - Changes under `go/` must pass formatting and tests.
 - The Go implementation focuses on fast serializers.
+- Keep `threadsafe.Fory` backed by `sync.Pool`. Direct struct and enum 
registration
+  by ID or name must initialize every instance, including replacements after 
GC.
+  Follow Java `ThreadLocalFory`'s registration callback semantics without 
copying
+  its thread-local storage or replacing the Go pool with a bounded pool.
+  Configure the first actual instance before pooling it; registration cannot
+  accumulate on arbitrary `sync.Pool.Get` results. Freeze successful callbacks
+  before the first root, apply them only when creating additional instances, 
and
+  leave pool hits free of registration replay. Preserve direct registration
+  examples rather than forcing callers to factories.
 - Go `ReadContext` intentionally defers codec errors to existing `HasError` or 
`CheckError`
   boundaries. After an error, work may continue only while it remains panic- 
and bounds-safe and
   cannot cause disproportionate work or allocation, publish state that 
survives root cleanup, or
diff --git a/ci/run_ci.sh b/ci/run_ci.sh
index ebdab570b..a56dc8337 100755
--- a/ci/run_ci.sh
+++ b/ci/run_ci.sh
@@ -422,7 +422,7 @@ case $1 in
     go)
       echo "Executing fory go tests for go"
       cd "$ROOT/go/fory"
-      go test -v ./...
+      go test -race -v ./...
       echo "Executing fory go tests succeeds"
     ;;
     format)
diff --git a/ci/tasks/go.py b/ci/tasks/go.py
index 3df17f81f..e4e960dc1 100644
--- a/ci/tasks/go.py
+++ b/ci/tasks/go.py
@@ -23,5 +23,5 @@ def run():
     """Run Go CI tasks."""
     logging.info("Executing fory go tests")
     common.cd_project_subdir("go/fory")
-    common.exec_cmd("go test -v")
+    common.exec_cmd("go test -race -v ./...")
     logging.info("Executing fory go tests succeeds")
diff --git a/docs/object-serialization/go/configuration.md 
b/docs/object-serialization/go/configuration.md
index 8f59db06e..50850328d 100644
--- a/docs/object-serialization/go/configuration.md
+++ b/docs/object-serialization/go/configuration.md
@@ -241,7 +241,7 @@ go func() {
 
 The thread-safe wrapper:
 
-- Uses `sync.Pool` internally for efficient instance reuse
+- Creates instances as needed and reuses them across goroutines
 - Automatically copies serialized data before returning
 - Accepts the same configuration options as `fory.New()`
 
diff --git a/docs/object-serialization/go/thread-safety.md 
b/docs/object-serialization/go/thread-safety.md
index 89b536766..a9ed08dfc 100644
--- a/docs/object-serialization/go/thread-safety.md
+++ b/docs/object-serialization/go/thread-safety.md
@@ -66,32 +66,11 @@ go func() {
 }()
 ```
 
-### How It Works
-
-The thread-safe wrapper uses `sync.Pool`:
-
-1. **Acquire**: Gets a Fory instance from the pool
-2. **Use**: Performs serialization/deserialization
-3. **Copy**: Copies result data (buffer will be reused)
-4. **Release**: Returns instance to pool
-
-```go
-// Simplified implementation
-func (f *Fory) Serialize(v any) ([]byte, error) {
-    fory := f.pool.Get().(*fory.Fory)
-    defer f.pool.Put(fory)
-
-    data, err := fory.Serialize(v)
-    if err != nil {
-        return nil, err
-    }
-
-    // Copy because underlying buffer will be reused
-    result := make([]byte, len(data))
-    copy(result, data)
-    return result, nil
-}
-```
+The wrapper creates instances as needed and reuses them across goroutines.
+Each operation exclusively borrows one instance and returns it afterward.
+Registered types remain available when garbage collection reclaims cached
+instances. Serialized output is copied before returning, so callers can retain
+it safely.
 
 ### API
 
@@ -114,32 +93,48 @@ err = threadsafe.Unmarshal(data, &target)
 
 ## Type Registration
 
-Type registration should be done before concurrent use:
+Register all types before the first serialization or deserialization 
operation. The
+first operation permanently freezes the wrapper's registrations, even if that
+operation fails. A later registration attempt returns an error.
 
 ```go
 f := threadsafe.New()
 
-// Register types BEFORE concurrent access
-f.RegisterStruct(User{}, 1)
-f.RegisterStruct(Order{}, 2)
+if err := f.RegisterStruct(User{}, 1); err != nil {
+    panic(err)
+}
+if err := f.RegisterStruct(Order{}, 2); err != nil {
+    panic(err)
+}
 
-// Now safe to use concurrently
+// All concurrent operations use the registered types.
 go func() {
-    f.Serialize(&User{ID: 1})
+    data, err := f.Serialize(&User{ID: 1})
+    // Use data and handle err.
+    _, _ = data, err
 }()
 ```
 
-### Thread-Safe Registration
+`RegisterStructByName`, `RegisterEnum`, and `RegisterEnumByName` are also
+available directly on the wrapper. Every registered type is available to all
+concurrent operations and remains registered across garbage collections.
 
-The thread-safe wrapper handles registration safely:
+For custom per-instance initialization, use `NewWithFactory`:
 
 ```go
-// Safe: Registration is synchronized
-f := threadsafe.New()
-f.RegisterStruct(User{}, 1)  // Thread-safe
+f := threadsafe.NewWithFactory(func() *fory.Fory {
+    inner := fory.New()
+    if err := inner.RegisterExtension(CustomType{}, 100, 
newCustomSerializer()); err != nil {
+        panic(err)
+    }
+    return inner
+})
 ```
 
-However, for best performance, register all types at startup before concurrent 
use.
+The factory may be called concurrently as additional instances are needed. It
+must return a fresh, identically configured instance on every call, with
+registrations completed before returning. Create stateful custom serializers
+separately for each instance.
 
 ## Zero-Copy Considerations
 
@@ -331,11 +326,11 @@ go func() {
 }()
 ```
 
-**Fix**: Register all types before concurrent use.
+**Fix**: Register all types before the first serialization or deserialization.
 
 ## Best Practices
 
-1. **Register types at startup**: Before any concurrent operations
+1. **Register types at startup**: Before the first serialization or 
deserialization
 2. **Clone data if keeping references**: With non-thread-safe instance
 3. **Use per-worker instances for hot paths**: Eliminates pool contention
 4. **Profile before optimizing**: Thread-safe overhead may be negligible
diff --git a/go/fory/threadsafe/fory.go b/go/fory/threadsafe/fory.go
index 4afdfa0de..8593d4b1d 100644
--- a/go/fory/threadsafe/fory.go
+++ b/go/fory/threadsafe/fory.go
@@ -15,19 +15,27 @@
 // specific language governing permissions and limitations
 // under the License.
 
-// Package threadsafe provides a thread-safe wrapper around Fory using 
sync.Pool.
+// Package threadsafe provides a thread-safe wrapper around Fory.
 package threadsafe
 
 import (
+       "fmt"
+       "reflect"
        "sync"
+       "sync/atomic"
 
        "github.com/apache/fory/go/fory"
 )
 
 // Fory is a thread-safe wrapper around fory.Fory using sync.Pool.
-// It provides the same API as fory.Fory but is safe for concurrent use.
+// Struct and enum registration use the same API as fory.Fory and must finish
+// before the first serialization or deserialization operation.
 type Fory struct {
-       pool sync.Pool
+       pool           sync.Pool
+       registrationMu sync.Mutex
+       started        atomic.Bool
+       first          *fory.Fory
+       callbacks      []func(*fory.Fory) error
 }
 
 // New creates a new thread-safe Fory instance.
@@ -37,28 +45,54 @@ func New(opts ...fory.Option) *Fory {
        })
 }
 
-// NewWithFactory creates a new thread-safe Fory instance using a custom 
factory.
+// NewWithFactory creates a thread-safe Fory using a custom factory.
+// The factory must return a fresh, identically configured Fory instance on 
every
+// call, with any custom registrations completed before returning. It may be
+// called concurrently when additional instances are needed.
 func NewWithFactory(factory func() *fory.Fory) *Fory {
        if factory == nil {
                panic("threadsafe.NewWithFactory requires a non-nil factory")
        }
        f := &Fory{}
-       f.pool = sync.Pool{
-               New: func() any {
-                       inner := factory()
-                       if inner == nil {
-                               panic("threadsafe.NewWithFactory factory 
returned nil")
+       f.pool.New = func() any {
+               inner := factory()
+               if inner == nil {
+                       panic("threadsafe.NewWithFactory factory returned nil")
+               }
+               // Like Java ThreadLocalFory's factoryCallback, registrations 
initialize
+               // every new instance, including replacements for entries 
discarded by GC.
+               // Setup holds registrationMu; root operations freeze callbacks 
before Get.
+               for _, callback := range f.callbacks {
+                       if err := callback(inner); err != nil {
+                               panic(fmt.Errorf("threadsafe factory 
registration failed: %w", err))
                        }
-                       return inner
-               },
+               }
+               return inner
        }
        return f
 }
 
 func (f *Fory) acquire() *fory.Fory {
+       if !f.started.Load() {
+               if inner := f.freezeRegistrations(); inner != nil {
+                       return inner
+               }
+       }
        return f.pool.Get().(*fory.Fory)
 }
 
+//go:noinline
+func (f *Fory) freezeRegistrations() *fory.Fory {
+       f.registrationMu.Lock()
+       defer f.registrationMu.Unlock()
+       // The first root directly borrows the instance configured during setup.
+       // Freeze even if that root fails, and let sync.Pool own its reuse 
afterward.
+       inner := f.first
+       f.first = nil
+       f.started.Store(true)
+       return inner
+}
+
 func (f *Fory) release(inner *fory.Fory) {
        inner.Reset()
        f.pool.Put(inner)
@@ -90,11 +124,75 @@ func (f *Fory) Deserialize(data []byte, v any) error {
        return inner.Deserialize(data, v)
 }
 
-// RegisterStructByName registers a struct type by name for cross-language 
serialization.
+func (f *Fory) registerCallback(registration func(*fory.Fory) error) error {
+       f.registrationMu.Lock()
+       defer f.registrationMu.Unlock()
+       if f.started.Load() {
+               return fmt.Errorf("types must be registered before the first 
serialization or deserialization")
+       }
+       // Registration cannot accumulate on arbitrary pool.Get results: even
+       // serialized callers can borrow different entries, and GC can discard 
them.
+       // Configure the first real instance before it enters the pool instead.
+       if f.first == nil {
+               f.first = f.pool.New().(*fory.Fory)
+       }
+       if err := registration(f.first); err != nil {
+               return err
+       }
+       f.callbacks = append(f.callbacks, registration)
+       return nil
+}
+
+// Keep only type metadata in callbacks, rather than retaining caller objects.
+func registrationType(type_ any) reflect.Type {
+       if typ, ok := type_.(reflect.Type); ok {
+               return typ
+       }
+       typ := reflect.TypeOf(type_)
+       if typ != nil && typ.Kind() == reflect.Ptr {
+               typ = typ.Elem()
+       }
+       return typ
+}
+
+// RegisterStruct registers a struct type with a numeric ID in every pooled 
instance.
+// Registration must complete before the first serialization or 
deserialization,
+// including a failed operation. Arguments follow fory.Fory.RegisterStruct.
+func (f *Fory) RegisterStruct(type_ any, typeID uint32) error {
+       typ := registrationType(type_)
+       return f.registerCallback(func(inner *fory.Fory) error {
+               return inner.RegisterStruct(typ, typeID)
+       })
+}
+
+// RegisterStructByName registers a struct type by name in every pooled 
instance.
+// Registration must complete before the first serialization or 
deserialization,
+// including a failed operation. Arguments follow 
fory.Fory.RegisterStructByName.
 func (f *Fory) RegisterStructByName(type_ any, name string) error {
-       inner := f.acquire()
-       defer f.release(inner)
-       return inner.RegisterStructByName(type_, name)
+       typ := registrationType(type_)
+       return f.registerCallback(func(inner *fory.Fory) error {
+               return inner.RegisterStructByName(typ, name)
+       })
+}
+
+// RegisterEnum registers an enum type with a numeric ID in every pooled 
instance.
+// Registration must complete before the first serialization or 
deserialization,
+// including a failed operation. Arguments follow fory.Fory.RegisterEnum.
+func (f *Fory) RegisterEnum(type_ any, typeID uint32) error {
+       typ := registrationType(type_)
+       return f.registerCallback(func(inner *fory.Fory) error {
+               return inner.RegisterEnum(typ, typeID)
+       })
+}
+
+// RegisterEnumByName registers an enum type by name in every pooled instance.
+// Registration must complete before the first serialization or 
deserialization,
+// including a failed operation. Arguments follow fory.Fory.RegisterEnumByName.
+func (f *Fory) RegisterEnumByName(type_ any, name string) error {
+       typ := registrationType(type_)
+       return f.registerCallback(func(inner *fory.Fory) error {
+               return inner.RegisterEnumByName(typ, name)
+       })
 }
 
 // ============================================================================
diff --git a/go/fory/threadsafe/fory_test.go b/go/fory/threadsafe/fory_test.go
index 37b4a0ecf..b7272d325 100644
--- a/go/fory/threadsafe/fory_test.go
+++ b/go/fory/threadsafe/fory_test.go
@@ -18,12 +18,260 @@
 package threadsafe
 
 import (
+       "reflect"
+       "runtime"
+       "sync"
+       "sync/atomic"
        "testing"
 
        "github.com/apache/fory/go/fory"
        "github.com/stretchr/testify/require"
 )
 
+func TestRegistrationAfterGC(t *testing.T) {
+       type Item struct{ Value int32 }
+       for _, byName := range []bool{false, true} {
+               name := "ID"
+               if byName {
+                       name = "Name"
+               }
+               t.Run(name, func(t *testing.T) {
+                       var created atomic.Int32
+                       f := NewWithFactory(func() *fory.Fory {
+                               created.Add(1)
+                               return fory.New()
+                       })
+                       if byName {
+                               require.NoError(t, 
f.RegisterStructByName(Item{}, "threadsafe.Item"))
+                       } else {
+                               require.NoError(t, f.RegisterStruct(Item{}, 1))
+                       }
+                       runtime.GC()
+                       runtime.GC()
+                       value := Item{Value: 42}
+                       data, err := f.Serialize(&value)
+                       require.NoError(t, err)
+                       // The configured instance serves the first root, even 
after setup GC.
+                       require.Equal(t, int32(1), created.Load())
+
+                       // Two collections discard the sync.Pool primary and 
victim caches.
+                       runtime.GC()
+                       runtime.GC()
+                       var result Item
+                       require.NoError(t, f.Deserialize(data, &result))
+                       require.Equal(t, value, result)
+                       require.Greater(t, created.Load(), int32(1))
+
+                       before := created.Load()
+                       runtime.GC()
+                       runtime.GC()
+                       data, err = Serialize(f, &value)
+                       require.NoError(t, err)
+                       require.Greater(t, created.Load(), before)
+                       require.NoError(t, Deserialize(f, data, &result))
+                       require.Equal(t, value, result)
+               })
+       }
+}
+
+func TestRegistrationInstances(t *testing.T) {
+       type Order struct{ ID int64 }
+       type User struct {
+               ID    int32
+               Order *Order
+       }
+       type NamedItem struct{ Name string }
+       type FactoryItem struct{ Name string }
+       type State int32
+       type Color int32
+       var factoryCalls atomic.Int32
+       var firstCreated *fory.Fory
+       f := NewWithFactory(func() *fory.Fory {
+               inner := fory.New()
+               if factoryCalls.Add(1) == 1 {
+                       firstCreated = inner
+               }
+               if err := inner.RegisterStruct(FactoryItem{}, 100); err != nil {
+                       panic(err)
+               }
+               return inner
+       })
+       require.Zero(t, factoryCalls.Load())
+       require.Error(t, f.RegisterStructByName(int32(0), "threadsafe.Invalid"))
+       require.NoError(t, f.RegisterStruct(&User{}, 1))
+       runtime.GC()
+       runtime.GC()
+       require.NoError(t, f.RegisterStruct(reflect.TypeOf(User{}), 1))
+       require.Error(t, f.RegisterStruct(Order{}, 1))
+       require.Error(t, f.RegisterStructByName(User{}, "threadsafe.Duplicate"))
+       require.NoError(t, f.RegisterStruct(Order{}, 2))
+       require.NoError(t, f.RegisterStructByName(NamedItem{}, 
"threadsafe.NamedItem"))
+       require.NoError(t, f.RegisterEnum(State(0), 3))
+       require.NoError(t, f.RegisterEnumByName(reflect.TypeOf(Color(0)), 
"threadsafe.Color"))
+       // Ordinary registration does not rebuild the factory's instances each 
time.
+       require.Equal(t, int32(1), factoryCalls.Load())
+
+       // Hold both borrows to force an additional, independently configured 
instance.
+       first := f.acquire()
+       second := f.acquire()
+       require.Same(t, firstCreated, first)
+       require.NotSame(t, first, second)
+       require.Equal(t, int32(2), factoryCalls.Load())
+       for _, inner := range []*fory.Fory{first, second} {
+               for _, value := range []any{&User{42, &Order{7}}, &Order{7}, 
&NamedItem{"name"}, &FactoryItem{"factory"}, State(1), Color(2)} {
+                       data, err := inner.Serialize(value)
+                       require.NoError(t, err)
+                       var result any
+                       require.NoError(t, inner.Deserialize(data, &result))
+                       require.Equal(t, value, result)
+               }
+       }
+       f.release(first)
+       f.release(second)
+
+       var workers sync.WaitGroup
+       for range 8 {
+               workers.Add(1)
+               go func() {
+                       defer workers.Done()
+                       value := User{42, &Order{7}}
+                       for range 20 {
+                               data, err := Serialize(f, &value)
+                               if err != nil {
+                                       t.Error(err)
+                                       return
+                               }
+                               var result User
+                               if err := Deserialize(f, data, &result); err != 
nil {
+                                       t.Error(err)
+                                       return
+                               }
+                               if result.ID != value.ID || result.Order == nil 
|| *result.Order != *value.Order {
+                                       t.Errorf("got %v, want %v", result, 
value)
+                                       return
+                               }
+                       }
+               }()
+       }
+       workers.Wait()
+}
+
+func TestConcurrentRegistration(t *testing.T) {
+       type User struct{ ID int32 }
+       type Order struct{ ID int64 }
+       type State int32
+       type Color int32
+       var created atomic.Int32
+       f := NewWithFactory(func() *fory.Fory {
+               created.Add(1)
+               return fory.New()
+       })
+       register := []func() error{
+               func() error { return f.RegisterStruct(User{}, 1) },
+               func() error { return f.RegisterStructByName(Order{}, 
"threadsafe.Order") },
+               func() error { return f.RegisterEnum(State(0), 2) },
+               func() error { return f.RegisterEnumByName(Color(0), 
"threadsafe.Color") },
+       }
+       var workers sync.WaitGroup
+       for _, call := range register {
+               workers.Add(1)
+               go func() {
+                       defer workers.Done()
+                       if err := call(); err != nil {
+                               t.Error(err)
+                       }
+               }()
+       }
+       workers.Wait()
+       require.Equal(t, int32(1), created.Load())
+       first := f.acquire()
+       second := f.acquire()
+       for _, inner := range []*fory.Fory{first, second} {
+               for _, value := range []any{&User{42}, &Order{7}, State(1), 
Color(2)} {
+                       data, err := inner.Serialize(value)
+                       require.NoError(t, err)
+                       var result any
+                       require.NoError(t, inner.Deserialize(data, &result))
+                       require.Equal(t, value, result)
+               }
+       }
+       f.release(first)
+       f.release(second)
+}
+
+func TestRegistrationAtFirstRoot(t *testing.T) {
+       type User struct{ ID int32 }
+       type Order struct{ ID int64 }
+       for range 20 {
+               f := New()
+               require.NoError(t, f.RegisterStruct(User{}, 1))
+               start := make(chan struct{})
+               registered := make(chan error, 1)
+               serialized := make(chan error, 1)
+               go func() {
+                       <-start
+                       registered <- f.RegisterStruct(Order{}, 2)
+               }()
+               go func() {
+                       <-start
+                       _, err := f.Serialize(&User{42})
+                       serialized <- err
+               }()
+               close(start)
+               registrationErr := <-registered
+               require.NoError(t, <-serialized)
+               first := f.acquire()
+               second := f.acquire()
+               for _, inner := range []*fory.Fory{first, second} {
+                       _, err := inner.Serialize(&Order{7})
+                       if registrationErr == nil {
+                               require.NoError(t, err)
+                       } else {
+                               require.Error(t, err)
+                       }
+               }
+               f.release(first)
+               f.release(second)
+       }
+}
+
+func TestRegistrationFreeze(t *testing.T) {
+       type Item struct{ Value int32 }
+       value := int32(42)
+       data, err := fory.New().Serialize(value)
+       require.NoError(t, err)
+       tests := []struct {
+               name string
+               fail bool
+               call func(*Fory) error
+       }{
+               {"Serialize", false, func(f *Fory) error { _, err := 
f.Serialize(value); return err }},
+               {"SerializeError", true, func(f *Fory) error { _, err := 
f.Serialize(Item{}); return err }},
+               {"GenericSerialize", false, func(f *Fory) error { _, err := 
Serialize(f, &value); return err }},
+               {"GenericSerializeError", true, func(f *Fory) error { _, err := 
Serialize(f, &Item{}); return err }},
+               {"Deserialize", false, func(f *Fory) error { return 
f.Deserialize(data, new(int32)) }},
+               {"DeserializeError", true, func(f *Fory) error { return 
f.Deserialize(nil, new(int32)) }},
+               {"GenericDeserialize", false, func(f *Fory) error { return 
Deserialize(f, data, new(int32)) }},
+               {"GenericDeserializeError", true, func(f *Fory) error { return 
Deserialize(f, nil, new(int32)) }},
+       }
+       for _, test := range tests {
+               t.Run(test.name, func(t *testing.T) {
+                       f := New()
+                       err := test.call(f)
+                       if test.fail {
+                               require.Error(t, err)
+                       } else {
+                               require.NoError(t, err)
+                       }
+                       require.Error(t, f.RegisterStruct(Item{}, 1))
+                       require.Error(t, f.RegisterStructByName(Item{}, 
"threadsafe.Item"))
+                       type State int32
+                       require.Error(t, f.RegisterEnum(State(0), 2))
+                       require.Error(t, f.RegisterEnumByName(State(0), 
"threadsafe.State"))
+               })
+       }
+}
+
 // TestFory tests the thread-safe Fory wrapper
 func TestFory(t *testing.T) {
        f := New(fory.WithXlang(false), fory.WithRefTracking(true), 
fory.WithCompatible(false))
@@ -140,6 +388,7 @@ func TestDeserialize(t *testing.T) {
        })
 
        t.Run("Slice", func(t *testing.T) {
+               f := New(fory.WithXlang(false), fory.WithRefTracking(true), 
fory.WithCompatible(false))
                // Serialize a struct containing the slice since *[]T is not 
supported
                type SliceWrapper struct {
                        Items []int32
diff --git a/python/pyfory/format/tests/test_vectorized.py 
b/python/pyfory/format/tests/test_vectorized.py
index 2af57e555..ba2bf49a1 100644
--- a/python/pyfory/format/tests/test_vectorized.py
+++ b/python/pyfory/format/tests/test_vectorized.py
@@ -17,8 +17,7 @@
 
 import pyfory as fory
 
-from pyfory.tests.core import require_pyarrow
-from pyfory.utils import lazy_import
+from pyfory.tests.core import pa, require_pyarrow
 from pyfory.format import (
     schema,
     field,
@@ -31,8 +30,6 @@ from pyfory.format import (
     to_arrow_schema,
 )
 
-pa = lazy_import("pyarrow")
-
 
 @require_pyarrow
 def test_vectorized():
diff --git a/python/pyfory/tests/record.py b/python/pyfory/tests/record.py
index 00546169f..ec929f298 100644
--- a/python/pyfory/tests/record.py
+++ b/python/pyfory/tests/record.py
@@ -20,9 +20,10 @@ from dataclasses import dataclass
 import pyfory as fory
 from typing import List, Dict
 
-from pyfory.utils import lazy_import
-
-pa = lazy_import("pyarrow")
+try:
+    import pyarrow as pa
+except ImportError:
+    pa = None
 
 
 class Foo:
diff --git a/python/pyfory/tests/test_buffer.py 
b/python/pyfory/tests/test_buffer.py
index f13590a2d..47a81388a 100644
--- a/python/pyfory/tests/test_buffer.py
+++ b/python/pyfory/tests/test_buffer.py
@@ -21,11 +21,9 @@ import pytest
 
 import pyfory
 from pyfory.serialization import ENABLE_FORY_CYTHON_SERIALIZATION, Buffer
-from pyfory.tests.core import require_pyarrow
+from pyfory.tests.core import pa, require_pyarrow
 from pyfory.tests.test_stream import OneByteStream
-from pyfory.utils import clear_bit, get_bit, lazy_import, set_bit, set_bit_to
-
-pa = lazy_import("pyarrow")
+from pyfory.utils import clear_bit, get_bit, set_bit, set_bit_to
 
 
 class RecvIntoOnlyStream:
diff --git a/python/pyfory/tests/test_cross_language.py 
b/python/pyfory/tests/test_cross_language.py
index e640b92bf..68a335776 100644
--- a/python/pyfory/tests/test_cross_language.py
+++ b/python/pyfory/tests/test_cross_language.py
@@ -30,14 +30,11 @@ try:
 except ImportError:
     pytest = None
 from dataclasses import dataclass
-from pyfory.utils import lazy_import
 from typing import List, Dict, Any
 
 
 import numpy as np
 
-pa = lazy_import("pyarrow")
-
 
 def debug_print(*params):
     """print params if debug is needed."""
@@ -213,6 +210,8 @@ def test_serialization_with_schema(schema_file_path, 
data_file_path):
 
 @cross_language_test
 def test_record_batch_basic(data_file_path):
+    import pyarrow as pa
+
     with open(data_file_path, "rb") as f:
         record_batch_bytes = f.read()
         buf = pa.py_buffer(record_batch_bytes)
@@ -225,6 +224,8 @@ def test_record_batch_basic(data_file_path):
 
 @cross_language_test
 def test_record_batch(data_file_path):
+    import pyarrow as pa
+
     with open(data_file_path, "rb") as f:
         record_batch_bytes = f.read()
         buf = pa.py_buffer(record_batch_bytes)
@@ -250,6 +251,8 @@ def test_record_batch(data_file_path):
 
 @cross_language_test
 def test_write_multi_record_batch(schema_file_path, data_file_path):
+    import pyarrow as pa
+
     with open(schema_file_path, "rb") as f:
         schema_bytes = f.read()
         schema = pa.ipc.read_schema(pa.py_buffer(schema_bytes))
diff --git a/python/pyfory/tests/test_serializer.py 
b/python/pyfory/tests/test_serializer.py
index 881561098..bb1d55cf0 100644
--- a/python/pyfory/tests/test_serializer.py
+++ b/python/pyfory/tests/test_serializer.py
@@ -46,9 +46,7 @@ from pyfory.serializer import (
     Numpy1DArraySerializer,
 )
 from pyfory.types import TypeId
-from pyfory.utils import lazy_import
-
-pa = lazy_import("pyarrow")
+from pyfory.tests.core import pa
 
 
 def test_compatible_mode_overrides():
diff --git a/python/pyfory/utils.py b/python/pyfory/utils.py
index c70082cef..2a262d7b7 100644
--- a/python/pyfory/utils.py
+++ b/python/pyfory/utils.py
@@ -15,69 +15,7 @@
 # specific language governing permissions and limitations
 # under the License.
 
-import importlib
-import inspect
 import sys
-from typing import Dict, Callable
-
-
-# This method is derived from 
https://github.com/mars-project/mars/blob/c36c53fa22e10ef9477d9c454401a2f281375f31/mars/utils.py.
-def lazy_import(
-    name: str,
-    package: str = None,
-    globals_: Dict = None,  # pylint: disable=redefined-builtin
-    locals_: Dict = None,  # pylint: disable=redefined-builtin
-    rename: str = None,
-    placeholder: bool = False,
-):
-    rename = rename or name
-    prefix_name = name.split(".", 1)[0]
-    globals_ = globals_ or inspect.currentframe().f_back.f_globals
-
-    class LazyModule(object):
-        def __init__(self):
-            self._on_loads = []
-
-        def __getattr__(self, item):
-            if item.startswith("_pytest") or item in ("__bases__", "__test__"):
-                raise AttributeError(item)
-
-            real_mod = importlib.import_module(name, package=package)
-            if rename in globals_:
-                globals_[rename] = real_mod
-            elif locals_ is not None:
-                locals_[rename] = real_mod
-            ret = getattr(real_mod, item)
-            for on_load_func in self._on_loads:
-                on_load_func()
-            # make sure on_load hooks only executed once
-            self._on_loads = []
-            return ret
-
-        def add_load_handler(self, func: Callable):
-            self._on_loads.append(func)
-            return func
-
-    if importlib.util.find_spec(prefix_name) is not None:
-        return LazyModule()
-    elif placeholder:
-        return ModulePlaceholder(prefix_name)
-    else:
-        return None
-
-
-class ModulePlaceholder:
-    def __init__(self, mod_name: str):
-        self._mod_name = mod_name
-
-    def _raises(self):
-        raise AttributeError(f"{self._mod_name} is required but not 
installed.")
-
-    def __getattr__(self, key):
-        self._raises()
-
-    def __call__(self, *_args, **_kwargs):
-        self._raises()
 
 
 is_little_endian = sys.byteorder == "little"
@@ -112,6 +50,5 @@ __all__ = [
     "set_bit",
     "clear_bit",
     "set_bit_to",
-    "lazy_import",
     "is_little_endian",
 ]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to