diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 08a8b8d4..cae808e3 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -36,7 +36,7 @@ jobs: - name: golangci-lint uses: golangci/golangci-lint-action@v8 with: - version: v2.4.0 + version: v2.11.4 args: --timeout=30m install-mode: binary verify: false \ No newline at end of file diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 3afe9684..e6da2b41 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -41,7 +41,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ~1.20 + go-version: ~1.26.0 continue-on-error: true - name: Build run: | @@ -57,7 +57,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ~1.21 + go-version: ~1.26.0 continue-on-error: true - name: Build run: | @@ -73,7 +73,7 @@ jobs: - name: Setup Go uses: actions/setup-go@v5 with: - go-version: ~1.22 + go-version: ~1.26.0 continue-on-error: true - name: Build run: | diff --git a/common/memory/memory.go b/common/memory/memory.go index 6fd5a25e..54ca2492 100644 --- a/common/memory/memory.go +++ b/common/memory/memory.go @@ -3,22 +3,23 @@ package memory import "runtime" func Total() uint64 { - if nativeAvailable { - return usageNative() - } - return Inuse() + return totalNative() } -func Inuse() uint64 { - var memStats runtime.MemStats - runtime.ReadMemStats(&memStats) - return memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased +func TotalAvailable() bool { + return totalAvailable() } func Available() uint64 { return availableNative() } -func AvailableSupported() bool { - return availableNativeSupported() +func AvailableAvailable() bool { + return availableAvailable() +} + +func Inuse() uint64 { + var memStats runtime.MemStats + runtime.ReadMemStats(&memStats) + return memStats.StackInuse + memStats.HeapInuse + memStats.HeapIdle - memStats.HeapReleased } diff --git a/common/memory/memory_available_darwin.go b/common/memory/memory_available_darwin.go deleted file mode 100644 index 0e42c2f1..00000000 --- a/common/memory/memory_available_darwin.go +++ /dev/null @@ -1,36 +0,0 @@ -package memory - -// #include -// #include -// -// static size_t get_available_memory(int *supported) { -// typedef size_t (*proc_available_memory_func)(void); -// static int resolved = 0; -// static proc_available_memory_func fn = NULL; -// if (!resolved) { -// fn = (proc_available_memory_func)dlsym(RTLD_DEFAULT, "os_proc_available_memory"); -// resolved = 1; -// } -// if (fn) { -// *supported = 1; -// return fn(); -// } -// *supported = 0; -// return 0; -// } -import "C" - -func availableNative() uint64 { - var supported C.int - result := C.get_available_memory(&supported) - if supported == 0 { - return 0 - } - return uint64(result) -} - -func availableNativeSupported() bool { - var supported C.int - C.get_available_memory(&supported) - return supported != 0 -} diff --git a/common/memory/memory_available_stub.go b/common/memory/memory_available_stub.go deleted file mode 100644 index 27ff4a1c..00000000 --- a/common/memory/memory_available_stub.go +++ /dev/null @@ -1,11 +0,0 @@ -//go:build !(darwin && cgo) && !linux - -package memory - -func availableNativeSupported() bool { - return false -} - -func availableNative() uint64 { - return 0 -} diff --git a/common/memory/memory_darwin.go b/common/memory/memory_darwin.go index 4fe2e50a..86b0be2a 100644 --- a/common/memory/memory_darwin.go +++ b/common/memory/memory_darwin.go @@ -1,18 +1,59 @@ package memory // #include +// #include +// #include +// +// typedef size_t (*proc_available_memory_func)(void); +// static int resolved = 0; +// static proc_available_memory_func fn = NULL; +// +// static void resolve_available_memory() { +// if (!resolved) { +// fn = (proc_available_memory_func)dlsym(RTLD_DEFAULT, "os_proc_available_memory"); +// resolved = 1; +// } +// } +// +// static size_t get_available_memory(int *supported) { +// resolve_available_memory(); +// if (fn) { +// *supported = 1; +// return fn(); +// } +// *supported = 0; +// return 0; +// } +// +// static int is_available_memory_supported() { +// resolve_available_memory(); +// return fn != NULL; +// } import "C" import "unsafe" -const nativeAvailable = true - -func usageNative() uint64 { - var memoryUsageInByte uint64 +func totalNative() uint64 { var vmInfo C.task_vm_info_data_t var count C.mach_msg_type_number_t = C.TASK_VM_INFO_COUNT - var kernelReturn C.kern_return_t = C.task_info(C.vm_map_t(C.mach_task_self_), C.TASK_VM_INFO, (*C.integer_t)(unsafe.Pointer(&vmInfo)), &count) - if kernelReturn == C.KERN_SUCCESS { - memoryUsageInByte = uint64(vmInfo.phys_footprint) + if C.task_info(C.vm_map_t(C.mach_task_self_), C.TASK_VM_INFO, (*C.integer_t)(unsafe.Pointer(&vmInfo)), &count) == C.KERN_SUCCESS { + return uint64(vmInfo.phys_footprint) + } + return 0 +} + +func totalAvailable() bool { + return true +} + +func availableNative() uint64 { + var supported C.int + result := C.get_available_memory(&supported) + if supported == 0 { + return 0 } - return memoryUsageInByte + return uint64(result) +} + +func availableAvailable() bool { + return C.is_available_memory_supported() != 0 } diff --git a/common/memory/memory_available_linux.go b/common/memory/memory_linux.go similarity index 66% rename from common/memory/memory_available_linux.go rename to common/memory/memory_linux.go index d4dd239a..64e65861 100644 --- a/common/memory/memory_available_linux.go +++ b/common/memory/memory_linux.go @@ -8,8 +8,41 @@ import ( "strings" ) -func availableNativeSupported() bool { - return true +var pageSize = uint64(os.Getpagesize()) + +func totalNative() uint64 { + fd, err := os.Open("/proc/self/statm") + if err != nil { + return 0 + } + defer fd.Close() + var buf [128]byte + n, _ := fd.Read(buf[:]) + if n == 0 { + return 0 + } + i := 0 + for i < n && buf[i] != ' ' { + i++ + } + i++ + var rss uint64 + for i < n && buf[i] >= '0' && buf[i] <= '9' { + rss = rss*10 + uint64(buf[i]-'0') + i++ + } + return rss * pageSize +} + +func totalAvailable() bool { + fd, err := os.Open("/proc/self/statm") + if err != nil { + return false + } + defer fd.Close() + var buf [1]byte + n, _ := fd.Read(buf[:]) + return n > 0 } func availableNative() uint64 { @@ -20,6 +53,19 @@ func availableNative() uint64 { return procMemAvailable() } +func availableAvailable() bool { + _, ok := cgroupAvailable() + if ok { + return true + } + fd, err := os.Open("/proc/meminfo") + if err != nil { + return false + } + fd.Close() + return true +} + func cgroupAvailable() (uint64, bool) { max, err := readCgroupUint("/sys/fs/cgroup/memory.max") if err == nil && max != math.MaxUint64 { diff --git a/common/memory/memory_stub.go b/common/memory/memory_stub.go index 3781f587..24a4b050 100644 --- a/common/memory/memory_stub.go +++ b/common/memory/memory_stub.go @@ -1,9 +1,19 @@ -//go:build (darwin && !cgo) || !darwin +//go:build (darwin && !cgo) || (!darwin && !linux && !windows) package memory -const nativeAvailable = false +func totalNative() uint64 { + return 0 +} + +func totalAvailable() bool { + return false +} -func usageNative() uint64 { +func availableNative() uint64 { return 0 } + +func availableAvailable() bool { + return false +} diff --git a/common/memory/memory_windows.go b/common/memory/memory_windows.go new file mode 100644 index 00000000..c0f3b0f0 --- /dev/null +++ b/common/memory/memory_windows.go @@ -0,0 +1,35 @@ +package memory + +import ( + "unsafe" + + "golang.org/x/sys/windows" +) + +func totalNative() uint64 { + var mem processMemoryCounters + mem.cb = uint32(unsafe.Sizeof(mem)) + err := getProcessMemoryInfo(windows.CurrentProcess(), &mem, mem.cb) + if err != nil { + return 0 + } + return uint64(mem.workingSetSize) +} + +func totalAvailable() bool { + return true +} + +func availableNative() uint64 { + var mem memoryStatusEx + mem.dwLength = uint32(unsafe.Sizeof(mem)) + err := globalMemoryStatusEx(&mem) + if err != nil { + return 0 + } + return mem.ullAvailPhys +} + +func availableAvailable() bool { + return true +} diff --git a/common/memory/syscall_windows.go b/common/memory/syscall_windows.go new file mode 100644 index 00000000..069ce011 --- /dev/null +++ b/common/memory/syscall_windows.go @@ -0,0 +1,34 @@ +package memory + +//go:generate go run golang.org/x/sys/windows/mkwinsyscall -output zsyscall_windows.go syscall_windows.go + +type processMemoryCounters struct { + cb uint32 + pageFaultCount uint32 + peakWorkingSetSize uintptr + workingSetSize uintptr + quotaPeakPagedPoolUsage uintptr + quotaPagedPoolUsage uintptr + quotaPeakNonPagedPoolUsage uintptr + quotaNonPagedPoolUsage uintptr + pagefileUsage uintptr + peakPagefileUsage uintptr +} + +type memoryStatusEx struct { + dwLength uint32 + dwMemoryLoad uint32 + ullTotalPhys uint64 + ullAvailPhys uint64 + ullTotalPageFile uint64 + ullAvailPageFile uint64 + ullTotalVirtual uint64 + ullAvailVirtual uint64 + ullAvailExtendedVirtual uint64 +} + +// https://learn.microsoft.com/en-us/windows/win32/api/psapi/nf-psapi-getprocessmemoryinfo +//sys getProcessMemoryInfo(process windows.Handle, ppsmemCounters *processMemoryCounters, cb uint32) (err error) = kernel32.K32GetProcessMemoryInfo + +// https://learn.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-globalmemorystatusex +//sys globalMemoryStatusEx(lpBuffer *memoryStatusEx) (err error) = kernel32.GlobalMemoryStatusEx diff --git a/common/memory/zsyscall_windows.go b/common/memory/zsyscall_windows.go new file mode 100644 index 00000000..33334a37 --- /dev/null +++ b/common/memory/zsyscall_windows.go @@ -0,0 +1,61 @@ +// Code generated by 'go generate'; DO NOT EDIT. + +package memory + +import ( + "syscall" + "unsafe" + + "golang.org/x/sys/windows" +) + +var _ unsafe.Pointer + +// Do the interface allocations only once for common +// Errno values. +const ( + errnoERROR_IO_PENDING = 997 +) + +var ( + errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) + errERROR_EINVAL error = syscall.EINVAL +) + +// errnoErr returns common boxed Errno values, to prevent +// allocations at runtime. +func errnoErr(e syscall.Errno) error { + switch e { + case 0: + return errERROR_EINVAL + case errnoERROR_IO_PENDING: + return errERROR_IO_PENDING + } + // TODO: add more here, after collecting data on the common + // error values see on Windows. (perhaps when running + // all.bat?) + return e +} + +var ( + modkernel32 = windows.NewLazySystemDLL("kernel32.dll") + + procGlobalMemoryStatusEx = modkernel32.NewProc("GlobalMemoryStatusEx") + procK32GetProcessMemoryInfo = modkernel32.NewProc("K32GetProcessMemoryInfo") +) + +func globalMemoryStatusEx(lpBuffer *memoryStatusEx) (err error) { + r1, _, e1 := syscall.Syscall(procGlobalMemoryStatusEx.Addr(), 1, uintptr(unsafe.Pointer(lpBuffer)), 0, 0) + if r1 == 0 { + err = errnoErr(e1) + } + return +} + +func getProcessMemoryInfo(process windows.Handle, ppsmemCounters *processMemoryCounters, cb uint32) (err error) { + r1, _, e1 := syscall.Syscall(procK32GetProcessMemoryInfo.Addr(), 3, uintptr(process), uintptr(unsafe.Pointer(ppsmemCounters)), uintptr(cb)) + if r1 == 0 { + err = errnoErr(e1) + } + return +} diff --git a/common/tls/config.go b/common/tls/config.go index 3bb16416..587b42c6 100644 --- a/common/tls/config.go +++ b/common/tls/config.go @@ -4,6 +4,7 @@ import ( "context" "crypto/tls" "net" + "time" ) type ( @@ -17,6 +18,8 @@ type Config interface { SetServerName(serverName string) NextProtos() []string SetNextProtos(nextProto []string) + HandshakeTimeout() time.Duration + SetHandshakeTimeout(timeout time.Duration) STDConfig() (*STDConfig, error) Client(conn net.Conn) (Conn, error) Clone() Config @@ -51,6 +54,11 @@ type Conn interface { } func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) { + if handshakeTimeout := config.HandshakeTimeout(); handshakeTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, handshakeTimeout) + defer cancel() + } if compatServer, isCompat := config.(ConfigCompat); isCompat { return compatServer.ClientHandshake(ctx, conn) } @@ -66,6 +74,11 @@ func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, e } func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) { + if handshakeTimeout := config.HandshakeTimeout(); handshakeTimeout > 0 { + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, handshakeTimeout) + defer cancel() + } if compatServer, isCompat := config.(ServerConfigCompat); isCompat { return compatServer.ServerHandshake(ctx, conn) }