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

vatsrahul1001 pushed a commit to branch v3-4-test
in repository https://gitbox.apache.org/repos/asf/airflow.git


The following commit(s) were added to refs/heads/v3-4-test by this push:
     new e25535ebfdb [v3-4-test] Fix Go SDK build on 32-bit targets (#75551) 
(#75742)
e25535ebfdb is described below

commit e25535ebfdba32bbfd66be5757c4c442f0839516
Author: Jason(Zhe-You) Liu <[email protected]>
AuthorDate: Tue Oct 27 18:45:10 2026 +0800

    [v3-4-test] Fix Go SDK build on 32-bit targets (#75551) (#75742)
    
    The Go SDK did not compile where `int` is 32 bits (`GOARCH=386`, `arm`,
    `mipsle`), so `airflow-go-pack --goarch=arm` could not build a bundle for a
    32-bit worker. `MaxFrameSize` and `MaxRegionSize` are `2^32 - 1`, which does
    not fit in a 32-bit `int` when compared with `len()` or passed to
    `fmt.Errorf`.
    
    Once it compiled, `parseRetentionDays` also misbehaved on 32-bit: it checked
    the value against the int64 range, so `default_retention_days = 3000000000`
    wrapped and failed with `must be >= 0, got -2147483648`. It is now bounded 
by
    the platform's `int`.
    
    CI now vets the Go SDK for a 32-bit target and, on x64 runners, runs its
    tests under `GOARCH=386`, so this cannot break again unnoticed.
    
    (cherry picked from commit 0d527b7226584bedd6ccb6785d9511ef366e4af5)
    
    Co-authored-by: ChickenBenny <[email protected]>
    Co-authored-by: Claude <[email protected]>
---
 .github/workflows/ci-amd.yml                  | 13 +++++++++++++
 .github/workflows/ci-arm.yml                  | 13 +++++++++++++
 go-sdk/cmd/airflow-go-pack/sources.go         |  2 +-
 go-sdk/internal/bundlefooter/footer.go        |  5 +++--
 go-sdk/pkg/execution/client.go                |  7 ++++---
 go-sdk/pkg/execution/client_test.go           | 17 ++++++++++++++---
 go-sdk/pkg/execution/frames.go                | 16 +++++++++++++---
 go-sdk/pkg/execution/frames_oversized_test.go |  4 +++-
 go-sdk/pkg/execution/frames_test.go           | 14 ++++++++++++++
 9 files changed, 78 insertions(+), 13 deletions(-)

diff --git a/.github/workflows/ci-amd.yml b/.github/workflows/ci-amd.yml
index b79663bbb9a..e56377e053b 100644
--- a/.github/workflows/ci-amd.yml
+++ b/.github/workflows/ci-amd.yml
@@ -1263,6 +1263,19 @@ jobs:
       - name: Run Go tests with the race detector
         working-directory: ./go-sdk
         run: gotestsum --format github-actions -- -race ./...
+      # Nothing else builds for a 32-bit `int` (386, arm, mipsle), where 
2^32-1 constants overflow
+      # and int64 values wrap. Vet type-checks on any host; the tests need one 
that runs 386.
+      - name: Vet Go SDK for a 32-bit target
+        working-directory: ./go-sdk
+        env:
+          GOARCH: "386"
+        run: go vet ./...
+      - name: Run Go tests on a 32-bit target
+        if: runner.arch == 'X64'
+        working-directory: ./go-sdk
+        env:
+          GOARCH: "386"
+        run: gotestsum --format github-actions ./...
       # Go modules have no central registry that pre-scans releases, so we 
scan our own
       # dependency graph against the Go vulnerability database. govulncheck is 
reachability
       # based, so it only fails on advisories that actually affect code we 
call. The version
diff --git a/.github/workflows/ci-arm.yml b/.github/workflows/ci-arm.yml
index 9ce7669b669..ba3a3733c0c 100644
--- a/.github/workflows/ci-arm.yml
+++ b/.github/workflows/ci-arm.yml
@@ -1146,6 +1146,19 @@ jobs:
       - name: Run Go tests with the race detector
         working-directory: ./go-sdk
         run: gotestsum --format github-actions -- -race ./...
+      # Nothing else builds for a 32-bit `int` (386, arm, mipsle), where 
2^32-1 constants overflow
+      # and int64 values wrap. Vet type-checks on any host; the tests need one 
that runs 386.
+      - name: Vet Go SDK for a 32-bit target
+        working-directory: ./go-sdk
+        env:
+          GOARCH: "386"
+        run: go vet ./...
+      - name: Run Go tests on a 32-bit target
+        if: runner.arch == 'X64'
+        working-directory: ./go-sdk
+        env:
+          GOARCH: "386"
+        run: gotestsum --format github-actions ./...
       # Go modules have no central registry that pre-scans releases, so we 
scan our own
       # dependency graph against the Go vulnerability database. govulncheck is 
reachability
       # based, so it only fails on advisories that actually affect code we 
call. The version
diff --git a/go-sdk/cmd/airflow-go-pack/sources.go 
b/go-sdk/cmd/airflow-go-pack/sources.go
index 3455b73cb17..0faed5f10b2 100644
--- a/go-sdk/cmd/airflow-go-pack/sources.go
+++ b/go-sdk/cmd/airflow-go-pack/sources.go
@@ -248,7 +248,7 @@ func layoutSources(
                if int64(region.Len()) > bundlefooter.MaxRegionSize {
                        return sourceLayout{}, nil, fmt.Errorf(
                                "source region too large after embedding %s 
(max %d bytes)",
-                               p, int64(bundlefooter.MaxRegionSize),
+                               p, bundlefooter.MaxRegionSize,
                        )
                }
        }
diff --git a/go-sdk/internal/bundlefooter/footer.go 
b/go-sdk/internal/bundlefooter/footer.go
index 6641ab3af08..1ef4c1e7d36 100644
--- a/go-sdk/internal/bundlefooter/footer.go
+++ b/go-sdk/internal/bundlefooter/footer.go
@@ -49,8 +49,9 @@ const (
        FooterVersion = 1
 
        // MaxRegionSize is the largest source or metadata region this footer
-       // format can address (uint32 length field).
-       MaxRegionSize = math.MaxUint32
+       // format can address (uint32 length field). Typed so that a use site
+       // never turns it into an int, which overflows where int is 32 bits.
+       MaxRegionSize int64 = math.MaxUint32
 )
 
 // Magic is the 8-byte ASCII tag that identifies a file as a bundle.
diff --git a/go-sdk/pkg/execution/client.go b/go-sdk/pkg/execution/client.go
index f8263a8236d..69a63b0eee3 100644
--- a/go-sdk/pkg/execution/client.go
+++ b/go-sdk/pkg/execution/client.go
@@ -125,10 +125,11 @@ func parseRetentionDays(raw string) (int, error) {
        days, err := strconv.Atoi(raw)
        if err != nil {
                f, floatErr := strconv.ParseFloat(raw, 64)
-               // A float outside int64 range (an infinity included) converts 
with an
+               // A float outside int range (an infinity included) converts 
with an
                // implementation-defined result, so it is rejected rather than 
turned
-               // into whatever day count this platform happens to produce.
-               if floatErr != nil || f != math.Trunc(f) || f >= math.MaxInt64 
|| f <= math.MinInt64 {
+               // into whatever day count this platform happens to produce. 
int is
+               // 32 bits on 386 and arm, so the bound is not int64's.
+               if floatErr != nil || f != math.Trunc(f) || f >= math.MaxInt || 
f <= math.MinInt {
                        return 0, fmt.Errorf(
                                "failed to convert value to int. Please check 
%q key in %q section. Current value: %q",
                                "default_retention_days",
diff --git a/go-sdk/pkg/execution/client_test.go 
b/go-sdk/pkg/execution/client_test.go
index ec61b474ec8..2db1a53f769 100644
--- a/go-sdk/pkg/execution/client_test.go
+++ b/go-sdk/pkg/execution/client_test.go
@@ -470,6 +470,8 @@ func TestResolveDefaultExpiry(t *testing.T) {
                unset   bool
                want    any
                wantErr string
+               // The timestamp only wraps for day counts that need a 64-bit 
int.
+               needs64BitInt bool
        }{
                {name: "unset is an error", unset: true, wantErr: "is not set"},
                {name: "honours supervisor value", env: "7", want: 
now.UTC().AddDate(0, 0, 7)},
@@ -487,14 +489,23 @@ func TestResolveDefaultExpiry(t *testing.T) {
                        wantErr: "failed to convert value to int",
                },
                {
-                       name:    "day count that wraps the timestamp is an 
error",
-                       env:     "9223372036854775807",
-                       wantErr: "overflows the expiry timestamp",
+                       name:    "out of int range is an error",
+                       env:     strconv.FormatUint(math.MaxInt+1, 10),
+                       wantErr: "failed to convert value to int",
+               },
+               {
+                       name:          "day count that wraps the timestamp is 
an error",
+                       env:           "9223372036854775807",
+                       wantErr:       "overflows the expiry timestamp",
+                       needs64BitInt: true,
                },
        }
 
        for _, tc := range tests {
                t.Run(tc.name, func(t *testing.T) {
+                       if tc.needs64BitInt && strconv.IntSize < 64 {
+                               t.Skip("requires 64-bit int")
+                       }
                        // Setenv registers the restore, so unsetting it here 
is undone after.
                        t.Setenv(defaultRetentionDaysEnv, tc.env)
                        if tc.unset {
diff --git a/go-sdk/pkg/execution/frames.go b/go-sdk/pkg/execution/frames.go
index c1f5e73052c..cf6efb2aaf6 100644
--- a/go-sdk/pkg/execution/frames.go
+++ b/go-sdk/pkg/execution/frames.go
@@ -22,6 +22,7 @@ import (
        "encoding/binary"
        "fmt"
        "io"
+       "math"
 
        "github.com/vmihailenco/msgpack/v5"
 
@@ -95,11 +96,11 @@ func writeFrame(w io.Writer, payload []byte) error {
        // below and put a corrupt length prefix on the wire, desynchronising
        // the peer instead of failing loudly here. Mirrors the OverflowError
        // raised by task-sdk comms.py:_FrameMixin.as_bytes.
-       if len(payload) > MaxFrameSize {
+       if uint64(len(payload)) > MaxFrameSize {
                return fmt.Errorf(
                        "frame payload length %d exceeds max %d",
                        len(payload),
-                       MaxFrameSize,
+                       uint64(MaxFrameSize),
                )
        }
        buf := make([]byte, 4+len(payload))
@@ -131,7 +132,16 @@ func readFrame(r io.Reader) (IncomingFrame, error) {
                return IncomingFrame{}, fmt.Errorf(
                        "frame payload length %d exceeds max %d",
                        payloadLen,
-                       MaxFrameSize,
+                       uint64(MaxFrameSize),
+               )
+       }
+       // Where int is 32 bits, a length past math.MaxInt wraps negative in 
int()
+       // and make panics instead of the read failing.
+       if uint64(payloadLen) > math.MaxInt {
+               return IncomingFrame{}, fmt.Errorf(
+                       "frame payload length %d exceeds this platform's max 
%d",
+                       payloadLen,
+                       math.MaxInt,
                )
        }
        payload := make([]byte, int(payloadLen))
diff --git a/go-sdk/pkg/execution/frames_oversized_test.go 
b/go-sdk/pkg/execution/frames_oversized_test.go
index 5b862167ef0..789b9d753fd 100644
--- a/go-sdk/pkg/execution/frames_oversized_test.go
+++ b/go-sdk/pkg/execution/frames_oversized_test.go
@@ -55,7 +55,9 @@ func TestWriteFrameRejectsOversizedPayload(t *testing.T) {
                t.Skip("requires 64-bit int to construct a slice longer than 
MaxFrameSize")
        }
        var backing byte
-       payload := unsafe.Slice(&backing, uint64(MaxFrameSize)+1)
+       // A variable, not a constant expression, so the file still compiles 
where int is 32 bits.
+       length := uint64(MaxFrameSize) + 1
+       payload := unsafe.Slice(&backing, length)
 
        err := writeFrame(&bytes.Buffer{}, payload)
        require.Error(t, err)
diff --git a/go-sdk/pkg/execution/frames_test.go 
b/go-sdk/pkg/execution/frames_test.go
index 6dd8d2a9a9a..c71505a5b60 100644
--- a/go-sdk/pkg/execution/frames_test.go
+++ b/go-sdk/pkg/execution/frames_test.go
@@ -20,6 +20,8 @@ package execution
 import (
        "bytes"
        "encoding/binary"
+       "math"
+       "strconv"
        "testing"
 
        "github.com/stretchr/testify/assert"
@@ -110,6 +112,18 @@ func TestWriteAndReadFrame(t *testing.T) {
        assert.True(t, isNilRaw(frame.Err))
 }
 
+func TestReadFrameRejectsLengthPastPlatformInt(t *testing.T) {
+       if strconv.IntSize == 64 {
+               t.Skip("a length past a 64-bit int does not fit the 4-byte 
prefix")
+       }
+       var buf bytes.Buffer
+       require.NoError(t, binary.Write(&buf, binary.BigEndian, 
uint32(math.MaxInt32)+1))
+
+       _, err := readFrame(&buf)
+       require.Error(t, err)
+       assert.Contains(t, err.Error(), "exceeds this platform's max")
+}
+
 func TestDecodeResponseFrame(t *testing.T) {
        // Encode a 3-element response frame: [id, body, error]
        var buf bytes.Buffer

Reply via email to