Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aeb9ac9a7f | ||
|
|
d97201a53d | ||
|
|
0468b8b0ad | ||
|
|
60f9e8454d | ||
|
|
45c3c1f20a | ||
|
|
439cdb4d4e |
16
README.md
16
README.md
@@ -23,10 +23,12 @@ _Gopher image by [Renee French][rf], licensed under [Creative Commons 4.0 Attrib
|
||||
|
||||
Every release includes the following modifications:
|
||||
|
||||
- Restored Windows 7 and Windows Server 2008 R2 support (reverted [693def1](https://github.com/golang/go/commit/693def151adff1af707d82d28f55dba81ceb08e1))
|
||||
- Switched back to RtlGenRandom from ProcessPrng, which breaks Win7/2008R2 (reverted [693def1](https://github.com/golang/go/commit/693def151adff1af707d82d28f55dba81ceb08e1))
|
||||
- Added back LoadLibraryA fallback to load system libraries (reverted [a17d959](https://github.com/golang/go/commit/a17d959debdb04cd550016a3501dd09d50cd62e7))
|
||||
- Added back sysSocket fallback for socket syscalls (reverted [7c1157f](https://github.com/golang/go/commit/7c1157f9544922e96945196b47b95664b1e39108))
|
||||
- Added back Windows 7 console handle workaround (reverted [48042aa](https://github.com/golang/go/commit/48042aa09c2f878c4faa576948b07fe625c4707a))
|
||||
- Restored deprecated `go get` behavior for use outside modules (reverted [de4d503](https://github.com/golang/go/commit/de4d50316fb5c6d1529aa5377dc93b26021ee843))
|
||||
- Includes all improvements and bug fixes from Go 1.23.1
|
||||
- Includes all improvements and bug fixes from the corresponding upstream Go release
|
||||
|
||||
The Windows binary provided here also supports Windows 7 and Windows Server 2008 R2
|
||||
|
||||
@@ -38,7 +40,7 @@ Binary distributions are **available at the [release page](https://github.com/th
|
||||
|
||||
#### Windows Installation
|
||||
|
||||
1. Download the `go-legacy-win7-<version>.windows-<arch>.zip` file.
|
||||
1. Download the `go-legacy-win7-<version>.windows_<arch>.zip` file.
|
||||
2. Extract the ZIP to `C:\` (or any preferred location). This will create a `go-legacy-win7` folder.
|
||||
3. Add the following to your system environment variables:
|
||||
- Add `C:\go-legacy-win7\bin` (or your chosen path) to the system `PATH`.
|
||||
@@ -49,15 +51,15 @@ Binary distributions are **available at the [release page](https://github.com/th
|
||||
|
||||
#### macOS and Linux Installation
|
||||
|
||||
1. Download the appropriate `go-legacy-win7-<version>.<os>-<arch>.tar.gz` file.
|
||||
1. Download the appropriate `go-legacy-win7-<version>.<os>_<arch>.tar.gz` file.
|
||||
|
||||
- For macOS: `go-legacy-win7-<version>.darwin-<arch>.tar.gz`
|
||||
- For Linux: `go-legacy-win7-<version>.linux-<arch>.tar.gz`
|
||||
- For macOS: `go-legacy-win7-<version>.darwin_<arch>.tar.gz`
|
||||
- For Linux: `go-legacy-win7-<version>.linux_<arch>.tar.gz`
|
||||
|
||||
2. Extract the archive to `/usr/local`:
|
||||
|
||||
```
|
||||
sudo tar -C /usr/local -xzf go-legacy-win7-<version>.<os>-<arch>.tar.gz
|
||||
sudo tar -C /usr/local -xzf go-legacy-win7-<version>.<os>_<arch>.tar.gz
|
||||
```
|
||||
|
||||
3. Add the following to your shell configuration file:
|
||||
|
||||
4
VERSION
4
VERSION
@@ -1,2 +1,2 @@
|
||||
go1.23.1
|
||||
time 2024-08-29T20:56:24Z
|
||||
go1.23.2
|
||||
time 2024-09-28T01:34:15Z
|
||||
|
||||
@@ -318,9 +318,9 @@ func containsClosure(f, c *ir.Func) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// Closures within function Foo are named like "Foo.funcN..."
|
||||
// Closures within function Foo are named like "Foo.funcN..." or "Foo-rangeN".
|
||||
// TODO(mdempsky): Better way to recognize this.
|
||||
fn := f.Sym().Name
|
||||
cn := c.Sym().Name
|
||||
return len(cn) > len(fn) && cn[:len(fn)] == fn && cn[len(fn)] == '.'
|
||||
return len(cn) > len(fn) && cn[:len(fn)] == fn && (cn[len(fn)] == '.' || cn[len(fn)] == '-')
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@ var (
|
||||
hostsFilePath = windows.GetSystemDirectory() + "/Drivers/etc/hosts"
|
||||
|
||||
// Placeholders for socket system calls.
|
||||
socketFunc func(int, int, int) (syscall.Handle, error) = syscall.Socket
|
||||
wsaSocketFunc func(int32, int32, int32, *syscall.WSAProtocolInfo, uint32, uint32) (syscall.Handle, error) = windows.WSASocket
|
||||
connectFunc func(syscall.Handle, syscall.Sockaddr) error = syscall.Connect
|
||||
listenFunc func(syscall.Handle, int) error = syscall.Listen
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !js && !plan9 && !wasip1 && !windows
|
||||
//go:build !js && !plan9 && !wasip1
|
||||
|
||||
package socktest_test
|
||||
|
||||
|
||||
22
src/net/internal/socktest/main_windows_test.go
Normal file
22
src/net/internal/socktest/main_windows_test.go
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package socktest_test
|
||||
|
||||
import "syscall"
|
||||
|
||||
var (
|
||||
socketFunc func(int, int, int) (syscall.Handle, error)
|
||||
closeFunc func(syscall.Handle) error
|
||||
)
|
||||
|
||||
func installTestHooks() {
|
||||
socketFunc = sw.Socket
|
||||
closeFunc = sw.Closesocket
|
||||
}
|
||||
|
||||
func uninstallTestHooks() {
|
||||
socketFunc = syscall.Socket
|
||||
closeFunc = syscall.Closesocket
|
||||
}
|
||||
@@ -9,6 +9,35 @@ import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Socket wraps syscall.Socket.
|
||||
func (sw *Switch) Socket(family, sotype, proto int) (s syscall.Handle, err error) {
|
||||
sw.once.Do(sw.init)
|
||||
so := &Status{Cookie: cookie(family, sotype, proto)}
|
||||
sw.fmu.RLock()
|
||||
f, _ := sw.fltab[FilterSocket]
|
||||
sw.fmu.RUnlock()
|
||||
af, err := f.apply(so)
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
s, so.Err = syscall.Socket(family, sotype, proto)
|
||||
if err = af.apply(so); err != nil {
|
||||
if so.Err == nil {
|
||||
syscall.Closesocket(s)
|
||||
}
|
||||
return syscall.InvalidHandle, err
|
||||
}
|
||||
sw.smu.Lock()
|
||||
defer sw.smu.Unlock()
|
||||
if so.Err != nil {
|
||||
sw.stats.getLocked(so.Cookie).OpenFailed++
|
||||
return syscall.InvalidHandle, so.Err
|
||||
}
|
||||
nso := sw.addLocked(s, family, sotype, proto)
|
||||
sw.stats.getLocked(nso.Cookie).Opened++
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// WSASocket wraps [syscall.WSASocket].
|
||||
func (sw *Switch) WSASocket(family, sotype, proto int32, protinfo *syscall.WSAProtocolInfo, group uint32, flags uint32) (s syscall.Handle, err error) {
|
||||
sw.once.Do(sw.init)
|
||||
|
||||
@@ -8,6 +8,7 @@ import "internal/poll"
|
||||
|
||||
var (
|
||||
// Placeholders for saving original socket system calls.
|
||||
origSocket = socketFunc
|
||||
origWSASocket = wsaSocketFunc
|
||||
origClosesocket = poll.CloseFunc
|
||||
origConnect = connectFunc
|
||||
@@ -17,6 +18,7 @@ var (
|
||||
)
|
||||
|
||||
func installTestHooks() {
|
||||
socketFunc = sw.Socket
|
||||
wsaSocketFunc = sw.WSASocket
|
||||
poll.CloseFunc = sw.Closesocket
|
||||
connectFunc = sw.Connect
|
||||
@@ -26,6 +28,7 @@ func installTestHooks() {
|
||||
}
|
||||
|
||||
func uninstallTestHooks() {
|
||||
socketFunc = origSocket
|
||||
wsaSocketFunc = origWSASocket
|
||||
poll.CloseFunc = origClosesocket
|
||||
connectFunc = origConnect
|
||||
|
||||
@@ -20,6 +20,20 @@ func maxListenerBacklog() int {
|
||||
func sysSocket(family, sotype, proto int) (syscall.Handle, error) {
|
||||
s, err := wsaSocketFunc(int32(family), int32(sotype), int32(proto),
|
||||
nil, 0, windows.WSA_FLAG_OVERLAPPED|windows.WSA_FLAG_NO_HANDLE_INHERIT)
|
||||
if err == nil {
|
||||
return s, nil
|
||||
}
|
||||
// WSA_FLAG_NO_HANDLE_INHERIT flag is not supported on some
|
||||
// old versions of Windows, see
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/ms742212(v=vs.85).aspx
|
||||
// for details. Just use syscall.Socket, if windows.WSASocket failed.
|
||||
// See ../syscall/exec_unix.go for description of ForkLock.
|
||||
syscall.ForkLock.RLock()
|
||||
s, err = socketFunc(family, sotype, proto)
|
||||
if err == nil {
|
||||
syscall.CloseOnExec(s)
|
||||
}
|
||||
syscall.ForkLock.RUnlock()
|
||||
if err != nil {
|
||||
return syscall.InvalidHandle, os.NewSyscallError("socket", err)
|
||||
}
|
||||
|
||||
@@ -29,6 +29,36 @@ type timer struct {
|
||||
astate atomic.Uint8 // atomic copy of state bits at last unlock
|
||||
state uint8 // state bits
|
||||
isChan bool // timer has a channel; immutable; can be read without lock
|
||||
|
||||
// isSending is used to handle races between running a
|
||||
// channel timer and stopping or resetting the timer.
|
||||
// It is used only for channel timers (t.isChan == true).
|
||||
// The lowest zero bit is set when about to send a value on the channel,
|
||||
// and cleared after sending the value.
|
||||
// The stop/reset code uses this to detect whether it
|
||||
// stopped the channel send.
|
||||
//
|
||||
// An isSending bit is set only when t.mu is held.
|
||||
// An isSending bit is cleared only when t.sendLock is held.
|
||||
// isSending is read only when both t.mu and t.sendLock are held.
|
||||
//
|
||||
// Setting and clearing Uint8 bits handles the case of
|
||||
// a timer that is reset concurrently with unlockAndRun.
|
||||
// If the reset timer runs immediately, we can wind up with
|
||||
// concurrent calls to unlockAndRun for the same timer.
|
||||
// Using matched bit set and clear in unlockAndRun
|
||||
// ensures that the value doesn't get temporarily out of sync.
|
||||
//
|
||||
// We use a uint8 to keep the timer struct small.
|
||||
// This means that we can only support up to 8 concurrent
|
||||
// runs of a timer, where a concurrent run can only occur if
|
||||
// we start a run, unlock the timer, the timer is reset to a new
|
||||
// value (or the ticker fires again), it is ready to run,
|
||||
// and it is actually run, all before the first run completes.
|
||||
// Since completing a run is fast, even 2 concurrent timer runs are
|
||||
// nearly impossible, so this should be safe in practice.
|
||||
isSending atomic.Uint8
|
||||
|
||||
blocked uint32 // number of goroutines blocked on timer's channel
|
||||
|
||||
// Timer wakes up at when, and then at when+period, ... (period > 0 only)
|
||||
@@ -431,6 +461,15 @@ func (t *timer) stop() bool {
|
||||
// Stop any future sends with stale values.
|
||||
// See timer.unlockAndRun.
|
||||
t.seq++
|
||||
|
||||
// If there is currently a send in progress,
|
||||
// incrementing seq is going to prevent that
|
||||
// send from actually happening. That means
|
||||
// that we should return true: the timer was
|
||||
// stopped, even though t.when may be zero.
|
||||
if t.isSending.Load() > 0 {
|
||||
pending = true
|
||||
}
|
||||
}
|
||||
t.unlock()
|
||||
if !async && t.isChan {
|
||||
@@ -525,6 +564,15 @@ func (t *timer) modify(when, period int64, f func(arg any, seq uintptr, delay in
|
||||
// Stop any future sends with stale values.
|
||||
// See timer.unlockAndRun.
|
||||
t.seq++
|
||||
|
||||
// If there is currently a send in progress,
|
||||
// incrementing seq is going to prevent that
|
||||
// send from actually happening. That means
|
||||
// that we should return true: the timer was
|
||||
// stopped, even though t.when may be zero.
|
||||
if t.isSending.Load() > 0 {
|
||||
pending = true
|
||||
}
|
||||
}
|
||||
t.unlock()
|
||||
if !async && t.isChan {
|
||||
@@ -1013,6 +1061,24 @@ func (t *timer) unlockAndRun(now int64) {
|
||||
}
|
||||
t.updateHeap()
|
||||
}
|
||||
|
||||
async := debug.asynctimerchan.Load() != 0
|
||||
var isSendingClear uint8
|
||||
if !async && t.isChan {
|
||||
// Tell Stop/Reset that we are sending a value.
|
||||
// Set the lowest zero bit.
|
||||
// We do this awkward step because atomic.Uint8
|
||||
// doesn't support Add or CompareAndSwap.
|
||||
// We only set bits with t locked.
|
||||
v := t.isSending.Load()
|
||||
i := sys.TrailingZeros8(^v)
|
||||
if i == 8 {
|
||||
throw("too many concurrent timer firings")
|
||||
}
|
||||
isSendingClear = 1 << i
|
||||
t.isSending.Or(isSendingClear)
|
||||
}
|
||||
|
||||
t.unlock()
|
||||
|
||||
if raceenabled {
|
||||
@@ -1028,7 +1094,6 @@ func (t *timer) unlockAndRun(now int64) {
|
||||
ts.unlock()
|
||||
}
|
||||
|
||||
async := debug.asynctimerchan.Load() != 0
|
||||
if !async && t.isChan {
|
||||
// For a timer channel, we want to make sure that no stale sends
|
||||
// happen after a t.stop or t.modify, but we cannot hold t.mu
|
||||
@@ -1044,6 +1109,10 @@ func (t *timer) unlockAndRun(now int64) {
|
||||
// and double-check that t.seq is still the seq value we saw above.
|
||||
// If not, the timer has been updated and we should skip the send.
|
||||
// We skip the send by reassigning f to a no-op function.
|
||||
//
|
||||
// The isSending field tells t.stop or t.modify that we have
|
||||
// started to send the value. That lets them correctly return
|
||||
// true meaning that no value was sent.
|
||||
lock(&t.sendLock)
|
||||
if t.seq != seq {
|
||||
f = func(any, uintptr, int64) {}
|
||||
@@ -1053,6 +1122,9 @@ func (t *timer) unlockAndRun(now int64) {
|
||||
f(arg, seq, delay)
|
||||
|
||||
if !async && t.isChan {
|
||||
// We are no longer sending a value.
|
||||
t.isSending.And(^isSendingClear)
|
||||
|
||||
unlock(&t.sendLock)
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@ import (
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// ForkLock is not used on Windows.
|
||||
var ForkLock sync.RWMutex
|
||||
|
||||
// EscapeArg rewrites command line argument s as prescribed
|
||||
@@ -317,6 +316,18 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
}
|
||||
}
|
||||
|
||||
info := _OSVERSIONINFOW{}
|
||||
info.osVersionInfoSize = uint32(unsafe.Sizeof(info))
|
||||
rtlGetVersion(&info)
|
||||
isWin7 := info.majorVersion < 6 || (info.majorVersion == 6 && info.minorVersion <= 1)
|
||||
// NT kernel handles are divisible by 4, with the bottom 3 bits left as
|
||||
// a tag. The fully set tag correlates with the types of handles we're
|
||||
// concerned about here. Except, the kernel will interpret some
|
||||
// special handle values, like -1, -2, and so forth, so kernelbase.dll
|
||||
// checks to see that those bottom three bits are checked, but that top
|
||||
// bit is not checked.
|
||||
isLegacyWin7ConsoleHandle := func(handle Handle) bool { return isWin7 && handle&0x10000003 == 3 }
|
||||
|
||||
p, _ := GetCurrentProcess()
|
||||
parentProcess := p
|
||||
if sys.ParentProcess != 0 {
|
||||
@@ -325,7 +336,15 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
fd := make([]Handle, len(attr.Files))
|
||||
for i := range attr.Files {
|
||||
if attr.Files[i] > 0 {
|
||||
err := DuplicateHandle(p, Handle(attr.Files[i]), parentProcess, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
|
||||
destinationProcessHandle := parentProcess
|
||||
|
||||
// On Windows 7, console handles aren't real handles, and can only be duplicated
|
||||
// into the current process, not a parent one, which amounts to the same thing.
|
||||
if parentProcess != p && isLegacyWin7ConsoleHandle(Handle(attr.Files[i])) {
|
||||
destinationProcessHandle = p
|
||||
}
|
||||
|
||||
err := DuplicateHandle(p, Handle(attr.Files[i]), destinationProcessHandle, &fd[i], 0, true, DUPLICATE_SAME_ACCESS)
|
||||
if err != nil {
|
||||
return 0, 0, err
|
||||
}
|
||||
@@ -356,6 +375,14 @@ func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle
|
||||
|
||||
fd = append(fd, sys.AdditionalInheritedHandles...)
|
||||
|
||||
// On Windows 7, console handles aren't real handles, so don't pass them
|
||||
// through to PROC_THREAD_ATTRIBUTE_HANDLE_LIST.
|
||||
for i := range fd {
|
||||
if isLegacyWin7ConsoleHandle(fd[i]) {
|
||||
fd[i] = 0
|
||||
}
|
||||
}
|
||||
|
||||
// The presence of a NULL handle in the list is enough to cause PROC_THREAD_ATTRIBUTE_HANDLE_LIST
|
||||
// to treat the entire list as empty, so remove NULL handles.
|
||||
j := 0
|
||||
|
||||
@@ -1169,3 +1169,13 @@ const (
|
||||
)
|
||||
|
||||
const UNIX_PATH_MAX = 108 // defined in afunix.h
|
||||
|
||||
// https://learn.microsoft.com/en-us/windows-hardware/drivers/ddi/wdm/ns-wdm-_osversioninfow
|
||||
type _OSVERSIONINFOW struct {
|
||||
osVersionInfoSize uint32
|
||||
majorVersion uint32
|
||||
minorVersion uint32
|
||||
buildNumber uint32
|
||||
platformId uint32
|
||||
csdVersion [128]uint16
|
||||
}
|
||||
|
||||
@@ -43,6 +43,7 @@ var (
|
||||
modkernel32 = NewLazyDLL(sysdll.Add("kernel32.dll"))
|
||||
modmswsock = NewLazyDLL(sysdll.Add("mswsock.dll"))
|
||||
modnetapi32 = NewLazyDLL(sysdll.Add("netapi32.dll"))
|
||||
modntdll = NewLazyDLL(sysdll.Add("ntdll.dll"))
|
||||
modsecur32 = NewLazyDLL(sysdll.Add("secur32.dll"))
|
||||
modshell32 = NewLazyDLL(sysdll.Add("shell32.dll"))
|
||||
moduserenv = NewLazyDLL(sysdll.Add("userenv.dll"))
|
||||
@@ -169,6 +170,7 @@ var (
|
||||
procNetGetJoinInformation = modnetapi32.NewProc("NetGetJoinInformation")
|
||||
procNetUserGetInfo = modnetapi32.NewProc("NetUserGetInfo")
|
||||
procGetUserNameExW = modsecur32.NewProc("GetUserNameExW")
|
||||
procRtlGetVersion = modntdll.NewProc("RtlGetVersion")
|
||||
procTranslateNameW = modsecur32.NewProc("TranslateNameW")
|
||||
procCommandLineToArgvW = modshell32.NewProc("CommandLineToArgvW")
|
||||
procGetUserProfileDirectoryW = moduserenv.NewProc("GetUserProfileDirectoryW")
|
||||
@@ -1228,6 +1230,11 @@ func GetUserNameEx(nameFormat uint32, nameBuffre *uint16, nSize *uint32) (err er
|
||||
return
|
||||
}
|
||||
|
||||
func rtlGetVersion(info *_OSVERSIONINFOW) {
|
||||
Syscall(procRtlGetVersion.Addr(), 1, uintptr(unsafe.Pointer(info)), 0, 0)
|
||||
return
|
||||
}
|
||||
|
||||
func TranslateName(accName *uint16, accNameFormat uint32, desiredNameFormat uint32, translatedName *uint16, nSize *uint32) (err error) {
|
||||
r1, _, e1 := Syscall6(procTranslateNameW.Addr(), 5, uintptr(unsafe.Pointer(accName)), uintptr(accNameFormat), uintptr(desiredNameFormat), uintptr(unsafe.Pointer(translatedName)), uintptr(unsafe.Pointer(nSize)), 0)
|
||||
if r1&0xff == 0 {
|
||||
|
||||
@@ -785,6 +785,68 @@ func TestAdjustTimers(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopResult(t *testing.T) {
|
||||
testStopResetResult(t, true)
|
||||
}
|
||||
|
||||
func TestResetResult(t *testing.T) {
|
||||
testStopResetResult(t, false)
|
||||
}
|
||||
|
||||
// Test that when racing between running a timer and stopping a timer Stop
|
||||
// consistently indicates whether a value can be read from the channel.
|
||||
// Issue #69312.
|
||||
func testStopResetResult(t *testing.T, testStop bool) {
|
||||
for _, name := range []string{"0", "1", "2"} {
|
||||
t.Run("asynctimerchan="+name, func(t *testing.T) {
|
||||
testStopResetResultGODEBUG(t, testStop, name)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testStopResetResultGODEBUG(t *testing.T, testStop bool, godebug string) {
|
||||
t.Setenv("GODEBUG", "asynctimerchan="+godebug)
|
||||
|
||||
stopOrReset := func(timer *Timer) bool {
|
||||
if testStop {
|
||||
return timer.Stop()
|
||||
} else {
|
||||
return timer.Reset(1 * Hour)
|
||||
}
|
||||
}
|
||||
|
||||
start := make(chan struct{})
|
||||
var wg sync.WaitGroup
|
||||
const N = 1000
|
||||
wg.Add(N)
|
||||
for range N {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
<-start
|
||||
for j := 0; j < 100; j++ {
|
||||
timer1 := NewTimer(1 * Millisecond)
|
||||
timer2 := NewTimer(1 * Millisecond)
|
||||
select {
|
||||
case <-timer1.C:
|
||||
if !stopOrReset(timer2) {
|
||||
// The test fails if this
|
||||
// channel read times out.
|
||||
<-timer2.C
|
||||
}
|
||||
case <-timer2.C:
|
||||
if !stopOrReset(timer1) {
|
||||
// The test fails if this
|
||||
// channel read times out.
|
||||
<-timer1.C
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
close(start)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Benchmark timer latency when the thread that creates the timer is busy with
|
||||
// other work and the timers must be serviced by other threads.
|
||||
// https://golang.org/issue/38860
|
||||
|
||||
173
test/fixedbugs/issue69434.go
Normal file
173
test/fixedbugs/issue69434.go
Normal file
@@ -0,0 +1,173 @@
|
||||
// run
|
||||
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"iter"
|
||||
"math/rand"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// WordReader is the struct that implements io.Reader
|
||||
type WordReader struct {
|
||||
scanner *bufio.Scanner
|
||||
}
|
||||
|
||||
// NewWordReader creates a new WordReader from an io.Reader
|
||||
func NewWordReader(r io.Reader) *WordReader {
|
||||
scanner := bufio.NewScanner(r)
|
||||
scanner.Split(bufio.ScanWords)
|
||||
return &WordReader{
|
||||
scanner: scanner,
|
||||
}
|
||||
}
|
||||
|
||||
// Read reads data from the input stream and returns a single lowercase word at a time
|
||||
func (wr *WordReader) Read(p []byte) (n int, err error) {
|
||||
if !wr.scanner.Scan() {
|
||||
if err := wr.scanner.Err(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return 0, io.EOF
|
||||
}
|
||||
word := wr.scanner.Text()
|
||||
cleanedWord := removeNonAlphabetic(word)
|
||||
if len(cleanedWord) == 0 {
|
||||
return wr.Read(p)
|
||||
}
|
||||
n = copy(p, []byte(cleanedWord))
|
||||
return n, nil
|
||||
}
|
||||
|
||||
// All returns an iterator allowing the caller to iterate over the WordReader using for/range.
|
||||
func (wr *WordReader) All() iter.Seq[string] {
|
||||
word := make([]byte, 1024)
|
||||
return func(yield func(string) bool) {
|
||||
var err error
|
||||
var n int
|
||||
for n, err = wr.Read(word); err == nil; n, err = wr.Read(word) {
|
||||
if !yield(string(word[:n])) {
|
||||
return
|
||||
}
|
||||
}
|
||||
if err != io.EOF {
|
||||
fmt.Fprintf(os.Stderr, "error reading word: %v\n", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// removeNonAlphabetic removes non-alphabetic characters from a word using strings.Map
|
||||
func removeNonAlphabetic(word string) string {
|
||||
return strings.Map(func(r rune) rune {
|
||||
if unicode.IsLetter(r) {
|
||||
return unicode.ToLower(r)
|
||||
}
|
||||
return -1
|
||||
}, word)
|
||||
}
|
||||
|
||||
// ProbabilisticSkipper determines if an item should be retained with probability 1/(1<<n)
|
||||
type ProbabilisticSkipper struct {
|
||||
n int
|
||||
counter uint64
|
||||
bitmask uint64
|
||||
}
|
||||
|
||||
// NewProbabilisticSkipper initializes the ProbabilisticSkipper
|
||||
func NewProbabilisticSkipper(n int) *ProbabilisticSkipper {
|
||||
pr := &ProbabilisticSkipper{n: n}
|
||||
pr.refreshCounter()
|
||||
return pr
|
||||
}
|
||||
|
||||
// check panics if pr.n is not the expected value
|
||||
func (pr *ProbabilisticSkipper) check(n int) {
|
||||
if pr.n != n {
|
||||
panic(fmt.Sprintf("check: pr.n != n %d != %d", pr.n, n))
|
||||
}
|
||||
}
|
||||
|
||||
// refreshCounter refreshes the counter with a new random value
|
||||
func (pr *ProbabilisticSkipper) refreshCounter() {
|
||||
if pr.n == 0 {
|
||||
pr.bitmask = ^uint64(0) // All bits set to 1
|
||||
} else {
|
||||
pr.bitmask = rand.Uint64()
|
||||
for i := 0; i < pr.n-1; i++ {
|
||||
pr.bitmask &= rand.Uint64()
|
||||
}
|
||||
}
|
||||
pr.counter = 64
|
||||
}
|
||||
|
||||
// ShouldSkip returns true with probability 1/(1<<n)
|
||||
func (pr *ProbabilisticSkipper) ShouldSkip() bool {
|
||||
remove := pr.bitmask&1 == 0
|
||||
pr.bitmask >>= 1
|
||||
pr.counter--
|
||||
if pr.counter == 0 {
|
||||
pr.refreshCounter()
|
||||
}
|
||||
return remove
|
||||
}
|
||||
|
||||
// EstimateUniqueWordsIter estimates the number of unique words using a probabilistic counting method
|
||||
func EstimateUniqueWordsIter(reader io.Reader, memorySize int) int {
|
||||
wordReader := NewWordReader(reader)
|
||||
words := make(map[string]struct{}, memorySize)
|
||||
|
||||
rounds := 0
|
||||
roundRemover := NewProbabilisticSkipper(1)
|
||||
wordSkipper := NewProbabilisticSkipper(rounds)
|
||||
wordSkipper.check(rounds)
|
||||
|
||||
for word := range wordReader.All() {
|
||||
wordSkipper.check(rounds)
|
||||
if wordSkipper.ShouldSkip() {
|
||||
delete(words, word)
|
||||
} else {
|
||||
words[word] = struct{}{}
|
||||
|
||||
if len(words) >= memorySize {
|
||||
rounds++
|
||||
|
||||
wordSkipper = NewProbabilisticSkipper(rounds)
|
||||
for w := range words {
|
||||
if roundRemover.ShouldSkip() {
|
||||
delete(words, w)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
wordSkipper.check(rounds)
|
||||
}
|
||||
|
||||
if len(words) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
invProbability := 1 << rounds
|
||||
estimatedUniqueWords := len(words) * invProbability
|
||||
return estimatedUniqueWords
|
||||
}
|
||||
|
||||
func main() {
|
||||
input := "Hello, world! This is a test. Hello, world, hello!"
|
||||
expectedUniqueWords := 6 // "hello", "world", "this", "is", "a", "test" (but "hello" and "world" are repeated)
|
||||
memorySize := 6
|
||||
|
||||
reader := strings.NewReader(input)
|
||||
estimatedUniqueWords := EstimateUniqueWordsIter(reader, memorySize)
|
||||
if estimatedUniqueWords != expectedUniqueWords {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
133
test/fixedbugs/issue69507.go
Normal file
133
test/fixedbugs/issue69507.go
Normal file
@@ -0,0 +1,133 @@
|
||||
// run
|
||||
|
||||
// Copyright 2024 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package main
|
||||
|
||||
func main() {
|
||||
err := run()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func run() error {
|
||||
methods := "AB"
|
||||
|
||||
type node struct {
|
||||
tag string
|
||||
choices []string
|
||||
}
|
||||
all := []node{
|
||||
{"000", permutations(methods)},
|
||||
}
|
||||
|
||||
next := 1
|
||||
for len(all) > 0 {
|
||||
cur := all[0]
|
||||
k := copy(all, all[1:])
|
||||
all = all[:k]
|
||||
|
||||
if len(cur.choices) == 1 {
|
||||
continue
|
||||
}
|
||||
|
||||
var bestM map[byte][]string
|
||||
bMax := len(cur.choices) + 1
|
||||
bMin := -1
|
||||
for sel := range selections(methods) {
|
||||
m := make(map[byte][]string)
|
||||
for _, order := range cur.choices {
|
||||
x := findFirstMatch(order, sel)
|
||||
m[x] = append(m[x], order)
|
||||
}
|
||||
|
||||
min := len(cur.choices) + 1
|
||||
max := -1
|
||||
for _, v := range m {
|
||||
if len(v) < min {
|
||||
min = len(v)
|
||||
}
|
||||
if len(v) > max {
|
||||
max = len(v)
|
||||
}
|
||||
}
|
||||
if max < bMax || (max == bMax && min > bMin) {
|
||||
bestM = m
|
||||
bMin = min
|
||||
bMax = max
|
||||
}
|
||||
}
|
||||
|
||||
if bMax == len(cur.choices) {
|
||||
continue
|
||||
}
|
||||
|
||||
cc := Keys(bestM)
|
||||
for c := range cc {
|
||||
choices := bestM[c]
|
||||
next++
|
||||
|
||||
switch c {
|
||||
case 'A':
|
||||
case 'B':
|
||||
default:
|
||||
panic("unexpected selector type " + string(c))
|
||||
}
|
||||
all = append(all, node{"", choices})
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func permutations(s string) []string {
|
||||
if len(s) <= 1 {
|
||||
return []string{s}
|
||||
}
|
||||
|
||||
var result []string
|
||||
for i, char := range s {
|
||||
rest := s[:i] + s[i+1:]
|
||||
for _, perm := range permutations(rest) {
|
||||
result = append(result, string(char)+perm)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type Seq[V any] func(yield func(V) bool)
|
||||
|
||||
func selections(s string) Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
for bits := 1; bits < 1<<len(s); bits++ {
|
||||
var choice string
|
||||
for j, char := range s {
|
||||
if bits&(1<<j) != 0 {
|
||||
choice += string(char)
|
||||
}
|
||||
}
|
||||
if !yield(choice) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func findFirstMatch(order, sel string) byte {
|
||||
for _, c := range order {
|
||||
return byte(c)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func Keys[Map ~map[K]V, K comparable, V any](m Map) Seq[K] {
|
||||
return func(yield func(K) bool) {
|
||||
for k := range m {
|
||||
if !yield(k) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user