From: =?utf-8?b?T3R0byBLZWvDpGzDpGluZW4=?= <otto@debian.org>
Date: Mon, 19 May 2025 12:47:49 -0700
Subject: Use atomic.Int64 for i386 compatibility

Use atomic.Int64 for scanCount to ensure thread-safe operations
on architectures where int64 is not guaranteed to be atomic,
specifically i386. This prevents potential data races when
updating the scan count concurrently.

This fixes test suite that failed on:

    --- FAIL: TestFromMapFormats (0.00s)
        --- FAIL: TestFromMapFormats/tiny (0.00s)
    panic: unaligned 64-bit atomic operation [recovered]
    	panic: unaligned 64-bit atomic operation
    goroutine 32 [running]:
    testing.tRunner.func1.2({0x82eb3c0, 0x86b5b68})
    	/usr/lib/go-1.24/src/testing/testing.go:1734 +0x283
    testing.tRunner.func1()
    	/usr/lib/go-1.24/src/testing/testing.go:1737 +0x415
    panic({0x82eb3c0, 0x86b5b68})
    	/usr/lib/go-1.24/src/runtime/panic.go:792 +0x103
    internal/runtime/atomic.panicUnaligned()
    	/usr/lib/go-1.24/src/internal/runtime/atomic/unaligned.go:8 +0x2d
    internal/runtime/atomic.Xadd64(0xa4021cc, 0x1)
    	/usr/lib/go-1.24/src/internal/runtime/atomic/atomic_386.s:125 +0x11
    github.com/xo/tblfmt.scanAndFormat({0x86b7418, 0xa83e000}, {0xa4340b8, 0x1, 0x1}, {0x86b6518, 0xa842000}, 0xa4021cc)
    	/builds/go-team/packages/golang-github-xo-tblfmt/debian/output/source_dir/debian/.build/upstream/src/github.com/xo/tblfmt/encode.go:1294 +0xa9
    github.com/xo/tblfmt.(*TableEncoder).nextResults(0xa4020f8)
    	/builds/go-team/packages/golang-github-xo-tblfmt/debian/output/source_dir/debian/.build/upstream/src/github.com/xo/tblfmt/encode.go:297 +0x197
    github.com/xo/tblfmt.(*TableEncoder).Encode(0xa4020f8, {0x86b6010, 0xa474060})
    	/builds/go-team/packages/golang-github-xo-tblfmt/debian/output/source_dir/debian/.build/upstream/src/github.com/xo/tblfmt/encode.go:156 +0x39c
    github.com/xo/tblfmt.(*TableEncoder).EncodeAll(0xa4020f8, {0x86b6010, 0xa474060})
    	/builds/go-team/packages/golang-github-xo-tblfmt/debian/output/source_dir/debian/.build/upstream/src/github.com/xo/tblfmt/encode.go:264 +0x35
    github.com/xo/tblfmt.TestFromMapFormats.func1(0xa4a58c8)
    	/builds/go-team/packages/golang-github-xo-tblfmt/debian/output/source_dir/debian/.build/upstream/src/github.com/xo/tblfmt/opts_test.go:114 +0xa81
    testing.tRunner(0xa4a58c8, 0xa488810)
    	/usr/lib/go-1.24/src/testing/testing.go:1792 +0x119
    created by testing.(*T).Run in goroutine 29
    	/usr/lib/go-1.24/src/testing/testing.go:1851 +0x468
    FAIL	github.com/xo/tblfmt	0.012s

Forwarded: https://github.com/xo/tblfmt/pull/51
---
 encode.go | 108 ++++++++++++++++++++++++++++++++------------------------------
 1 file changed, 55 insertions(+), 53 deletions(-)

diff --git a/encode.go b/encode.go
index 682be43..39ff798 100644
--- a/encode.go
+++ b/encode.go
@@ -71,7 +71,7 @@ type TableEncoder struct {
 	// if height or width is greater than minPagerHeight and minPagerWidth,
 	pagerCmd string
 	// scanCount is the number of scanned results in the result set.
-	scanCount int64
+	scanCount atomic.Int64
 	// lowerColumnNames indicates lower casing the column names when column
 	// names are all caps.
 	lowerColumnNames bool
@@ -125,7 +125,7 @@ func NewTableEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) {
 // options specified in the encoder.
 func (enc *TableEncoder) Encode(w io.Writer) error {
 	// reset scan count
-	enc.scanCount = 0
+	enc.scanCount.Store(0)
 	enc.w = bufio.NewWriterSize(w, 2048)
 	if enc.resultSet == nil {
 		return ErrResultSetIsNil
@@ -185,12 +185,12 @@ func (enc *TableEncoder) Encode(w io.Writer) error {
 				exp.w = bufio.NewWriterSize(cmdBuf, 2048)
 			}
 			if err := exp.encodeVals(vals); err != nil {
-				return checkErr(err, cmd)
+							return checkErr(err, cmd)
 			}
 			continue
 		}
 		if enc.pagerCmd != "" && cmd == nil &&
-			((enc.minPagerHeight != 0 && enc.tableHeight(vals) >= enc.minPagerHeight) ||
+			((enc.minPagerHeight != 0 && enc.tableHeight(vals, enc.scanCount.Load()) >= enc.minPagerHeight) ||
 				(enc.minPagerWidth != 0 && enc.tableWidth() >= enc.minPagerWidth)) {
 			cmd, cmdBuf, err = startPager(enc.pagerCmd, w)
 			if err != nil {
@@ -200,6 +200,7 @@ func (enc *TableEncoder) Encode(w io.Writer) error {
 		}
 		// print header if not already done
 		if !wroteHeader {
+
 			wroteHeader = true
 			enc.header()
 		}
@@ -212,7 +213,7 @@ func (enc *TableEncoder) Encode(w io.Writer) error {
 		}
 	}
 	// add summary
-	if err := summarize(enc.w, enc.summary, enc.scanCount); err != nil {
+	if err := summarize(enc.w, enc.summary, enc.scanCount.Load()); err != nil {
 		return err
 	}
 	if err := enc.w.Flush(); err != nil {
@@ -223,6 +224,7 @@ func (enc *TableEncoder) Encode(w io.Writer) error {
 		return cmd.Wait()
 	}
 	return nil
+
 }
 
 func startPager(pagerCmd string, w io.Writer) (*exec.Cmd, io.WriteCloser, error) {
@@ -430,42 +432,42 @@ func (enc *TableEncoder) tableWidth() int {
 }
 
 // tableHeight calculates total table height.
-func (enc *TableEncoder) tableHeight(rows [][]*Value) int {
-	height := 0
-	if enc.title != nil && enc.title.Width != 0 {
-		height += strings.Count(string(enc.title.Buf), "\n")
-	}
-	// top border
-	if enc.border >= 2 && !enc.inline {
-		height++
-	}
-	// header
-	height++
-	// mid divider
-	if enc.inline {
-		height++
-	}
-	for _, row := range rows {
-		largest := 1
-		for _, cell := range row {
-			if cell == nil {
-				cell = enc.empty
-			}
-			if len(cell.Newlines) > largest {
-				largest = len(cell.Newlines)
-			}
-		}
-		height += largest
-	}
-	// end border
-	if enc.border >= 2 {
-		height++
-	}
-	// scanCount at this point is not the final value but this is better than nothing
-	if enc.summary != nil && enc.summary[-1] != nil || enc.summary[int(enc.scanCount)] != nil {
-		height++
-	}
-	return height
+func (enc *TableEncoder) tableHeight(rows [][]*Value, scanCount int64) int {
+  height := 0
+  if enc.title != nil && enc.title.Width != 0 {
+    height += strings.Count(string(enc.title.Buf), "\n")
+  }
+  // top border
+  if enc.border >= 2 && !enc.inline {
+    height++
+  }
+  // header
+  height++
+  // mid divider
+  if enc.inline {
+    height++
+  }
+  for _, row := range rows {
+	  largest := 1
+	  for _, cell := range row {
+	    if cell == nil {
+        cell = enc.empty
+	    }
+	    if len(cell.Newlines) > largest {
+        largest = len(cell.Newlines)
+	    }
+	  }
+	  height += largest
+  }
+  // end border
+  if enc.border >= 2 {
+    height++
+  }
+  // scanCount at this point is not the final value but this is better than nothing
+  if enc.summary != nil && enc.summary[-1] != nil || enc.summary[int(scanCount)] != nil {
+    height++
+  }
+  return height
 }
 
 // row draws the a table row.
@@ -590,7 +592,7 @@ func NewExpandedEncoder(resultSet ResultSet, opts ...Option) (Encoder, error) {
 // options specified in the encoder.
 func (enc *ExpandedEncoder) Encode(w io.Writer) error {
 	// reset scan count
-	enc.scanCount = 0
+	enc.scanCount.Store(0)
 	enc.w = bufio.NewWriterSize(w, 2048)
 	if enc.resultSet == nil {
 		return ErrResultSetIsNil
@@ -647,7 +649,7 @@ func (enc *ExpandedEncoder) Encode(w io.Writer) error {
 		}
 	}
 	// add summary
-	if err := summarize(w, enc.summary, enc.scanCount); err != nil {
+	if err := summarize(w, enc.summary, enc.scanCount.Load()); err != nil {
 		return err
 	}
 	if err := enc.w.Flush(); err != nil {
@@ -673,7 +675,7 @@ func (enc *ExpandedEncoder) encodeVals(vals [][]*Value) error {
 		}
 	}
 	// draw end border
-	if enc.border >= 2 && enc.scanCount != 0 {
+	if enc.border >= 2 && enc.scanCount.Load() != 0 {
 		enc.divider(enc.rowStyle(enc.lineStyle.End))
 	}
 	return nil
@@ -739,11 +741,11 @@ func (enc *ExpandedEncoder) tableHeight(rows [][]*Value) int {
 		}
 	}
 	// end border
-	if enc.border >= 2 {
+	if enc.border >= 2 && enc.scanCount.Load() != 0 {
 		height++
 	}
 	// scanCount at this point is not the final value but this is better than nothing
-	if enc.summary != nil && enc.summary[-1] != nil || enc.summary[int(enc.scanCount)] != nil {
+	if enc.summary != nil && (enc.summary[-1] != nil || enc.summary[int(enc.scanCount.Load())] != nil) {
 		height++
 	}
 	return height
@@ -865,9 +867,9 @@ func (enc *JSONEncoder) Encode(w io.Writer) error {
 	// process
 	var v *Value
 	var vals []*Value
-	var count int64
+	var count atomic.Int64
 	for enc.resultSet.Next() {
-		if count != 0 {
+		if count.Load() != 0 {
 			if _, err = w.Write(cma); err != nil {
 				return err
 			}
@@ -1070,7 +1072,7 @@ func (enc *UnalignedEncoder) Encode(w io.Writer) error {
 		return err
 	}
 	// process
-	var count int64
+	var count atomic.Int64
 	for enc.resultSet.Next() {
 		vals, err := scanAndFormat(enc.resultSet, r, enc.formatter, &count)
 		if err != nil {
@@ -1098,7 +1100,7 @@ func (enc *UnalignedEncoder) Encode(w io.Writer) error {
 			return err
 		}
 	}
-	if err := summarize(w, enc.summary, count); err != nil {
+	if err := summarize(w, enc.summary, count.Load()); err != nil {
 		return err
 	}
 	return enc.resultSet.Err()
@@ -1212,7 +1214,7 @@ func (enc *TemplateEncoder) Encode(w io.Writer) error {
 	}
 	// process
 	var rows [][]*Value
-	var count int64
+	var count atomic.Int64
 	for enc.resultSet.Next() {
 		vals, err := scanAndFormat(enc.resultSet, r, enc.formatter, &count)
 		if err != nil {
@@ -1284,14 +1286,14 @@ func newErrEncoder(_ ResultSet, opts ...Option) (Encoder, error) {
 }
 
 // scanAndFormat scans and formats values from the result set.
-func scanAndFormat(resultSet ResultSet, vals []any, formatter Formatter, count *int64) ([]*Value, error) {
+func scanAndFormat(resultSet ResultSet, vals []any, formatter Formatter, count *atomic.Int64) ([]*Value, error) {
 	if err := resultSet.Err(); err != nil {
 		return nil, err
 	}
 	if err := resultSet.Scan(vals...); err != nil {
 		return nil, err
 	}
-	atomic.AddInt64(count, 1)
+	count.Add(1)
 	return formatter.Format(vals)
 }
 
