Initial QSfera import
This commit is contained in:
@@ -0,0 +1 @@
|
||||
dist
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
builds:
|
||||
- skip: true
|
||||
+28
@@ -0,0 +1,28 @@
|
||||
BSD 3-Clause License
|
||||
|
||||
Copyright (c) 2025, Kovid Goyal
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of the copyright holder nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# go-parallel
|
||||
|
||||
Utility functions to make running code in parallel easier and safer.
|
||||
Panics in go routines are turned into regular errors, instead of crashing
|
||||
the program.
|
||||
+219
@@ -0,0 +1,219 @@
|
||||
package parallel
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"runtime"
|
||||
"slices"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
)
|
||||
|
||||
var _ = fmt.Print
|
||||
|
||||
type PanicError struct {
|
||||
frames []runtime.Frame
|
||||
panic_value any
|
||||
}
|
||||
|
||||
const indent_lead = " "
|
||||
|
||||
func format_frame_line(frame runtime.Frame) string {
|
||||
return fmt.Sprintf("\r\n%s%s%s:%d", indent_lead, frame.Function, frame.File, frame.Line)
|
||||
}
|
||||
|
||||
func (e *PanicError) walk(level int, yield func(string) bool) bool {
|
||||
s := "Panic"
|
||||
cause := fmt.Sprintf("%v", e.panic_value)
|
||||
if _, ok := e.panic_value.(*PanicError); ok {
|
||||
cause = "sub-panic (see below)"
|
||||
}
|
||||
if level > 0 {
|
||||
s = "\r\n--> Sub-panic"
|
||||
}
|
||||
if !yield(fmt.Sprintf("%s caused by: %s\r\nStack trace (most recent call first):", s, cause)) {
|
||||
return false
|
||||
}
|
||||
for _, f := range e.frames {
|
||||
if !yield(format_frame_line(f)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
if sp, ok := e.panic_value.(*PanicError); ok {
|
||||
return sp.walk(level+1, yield)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (e *PanicError) lines() iter.Seq[string] {
|
||||
return func(yield func(string) bool) {
|
||||
e.walk(0, yield)
|
||||
}
|
||||
}
|
||||
|
||||
func (e *PanicError) Error() string {
|
||||
return strings.Join(slices.Collect(e.lines()), "")
|
||||
}
|
||||
|
||||
func (e *PanicError) Unwrap() error {
|
||||
if ans, ok := e.panic_value.(*PanicError); ok {
|
||||
return ans
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Format a stack trace on panic and return it as an error
|
||||
func Format_stacktrace_on_panic(r any, skip_frames int) (err *PanicError) {
|
||||
pcs := make([]uintptr, 512)
|
||||
n := runtime.Callers(2+skip_frames, pcs)
|
||||
var ans []runtime.Frame
|
||||
frames := runtime.CallersFrames(pcs[:n])
|
||||
found_first_frame := false
|
||||
for frame, more := frames.Next(); more; frame, more = frames.Next() {
|
||||
if !found_first_frame {
|
||||
if strings.HasPrefix(frame.Function, "runtime.") {
|
||||
continue
|
||||
}
|
||||
found_first_frame = true
|
||||
}
|
||||
ans = append(ans, frame)
|
||||
}
|
||||
return &PanicError{frames: ans, panic_value: r}
|
||||
}
|
||||
|
||||
// Run the specified function in parallel over chunks from the specified range.
|
||||
// If the function panics, it is turned into a regular error. If multiple function calls panic,
|
||||
// any one of the panics will be returned.
|
||||
func Run_in_parallel_over_range(num_procs int, f func(int, int), start, limit int) (err error) {
|
||||
num_items := limit - start
|
||||
if num_procs <= 0 {
|
||||
num_procs = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
num_procs = max(1, min(num_procs, num_items))
|
||||
if num_procs < 2 {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = Format_stacktrace_on_panic(r, 1)
|
||||
}
|
||||
}()
|
||||
f(start, limit)
|
||||
return
|
||||
}
|
||||
err_once := sync.Once{}
|
||||
chunk_sz := max(1, num_items/num_procs)
|
||||
var wg sync.WaitGroup
|
||||
for start < limit {
|
||||
end := min(start+chunk_sz, limit)
|
||||
wg.Add(1)
|
||||
go func(start, end int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
perr := Format_stacktrace_on_panic(r, 1)
|
||||
err_once.Do(func() { err = perr })
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
f(start, end)
|
||||
}(start, end)
|
||||
start = end
|
||||
}
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// Run the specified function in parallel over chunks from the specified range.
|
||||
// If the function panics, it is turned into a regular error. If the function
|
||||
// returns an error it is returned. If multiple function calls panic or return errors,
|
||||
// any one of them will be returned.
|
||||
func Run_in_parallel_over_range_with_error(num_procs int, f func(int, int) error, start, limit int) (err error) {
|
||||
num_items := limit - start
|
||||
if num_procs <= 0 {
|
||||
num_procs = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
num_procs = max(1, min(num_procs, num_items))
|
||||
if num_procs < 2 {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = Format_stacktrace_on_panic(r, 1)
|
||||
}
|
||||
}()
|
||||
err = f(start, limit)
|
||||
return
|
||||
}
|
||||
chunk_sz := max(1, num_items/num_procs)
|
||||
var wg sync.WaitGroup
|
||||
err_once := sync.Once{}
|
||||
for start < limit {
|
||||
end := min(start+chunk_sz, limit)
|
||||
wg.Add(1)
|
||||
go func(start, end int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
perr := Format_stacktrace_on_panic(r, 1)
|
||||
err_once.Do(func() { err = perr })
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
if cerr := f(start, end); cerr != nil {
|
||||
err_once.Do(func() { err = cerr })
|
||||
}
|
||||
}(start, end)
|
||||
start = end
|
||||
}
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
// Run the specified function in parallel until one of them returns true.
|
||||
// The functions are passed a keep_going variable that they should periodically check and return false if it is false.
|
||||
// If any of the functions panic, the panic is turned into a regular error and returned.
|
||||
func Run_in_parallel_to_first_result(num_procs int, f func(start, limit int, keep_going *atomic.Bool) bool, start, limit int) (err error) {
|
||||
var keep_going atomic.Bool
|
||||
keep_going.Store(true)
|
||||
num_items := limit - start
|
||||
if num_procs <= 0 {
|
||||
num_procs = runtime.GOMAXPROCS(0)
|
||||
}
|
||||
num_procs = max(1, min(num_procs, num_items))
|
||||
if num_procs < 2 {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = Format_stacktrace_on_panic(r, 1)
|
||||
}
|
||||
}()
|
||||
f(start, limit, &keep_going)
|
||||
return
|
||||
}
|
||||
chunk_sz := max(1, num_items/num_procs)
|
||||
var wg sync.WaitGroup
|
||||
var err_once sync.Once
|
||||
ch := make(chan bool, num_items/chunk_sz+1)
|
||||
for start < limit {
|
||||
end := min(start+chunk_sz, limit)
|
||||
wg.Add(1)
|
||||
go func(start, end int) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
perr := Format_stacktrace_on_panic(r, 1)
|
||||
err_once.Do(func() { err = perr })
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
ch <- f(start, end, &keep_going)
|
||||
}(start, end)
|
||||
start = end
|
||||
}
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(ch)
|
||||
}()
|
||||
for x := range ch {
|
||||
if x {
|
||||
break
|
||||
}
|
||||
}
|
||||
keep_going.Store(false)
|
||||
wg.Wait()
|
||||
return
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python
|
||||
# License: GPLv3 Copyright: 2024, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
VERSION = '1.1.1'
|
||||
|
||||
|
||||
def run(*args: str):
|
||||
cp = subprocess.run(args)
|
||||
if cp.returncode != 0:
|
||||
raise SystemExit(cp.returncode)
|
||||
|
||||
|
||||
def main():
|
||||
try:
|
||||
ans = input(f'Publish version \033[91m{VERSION}\033[m (y/n): ')
|
||||
except KeyboardInterrupt:
|
||||
ans = 'n'
|
||||
if ans.lower() != 'y':
|
||||
return
|
||||
os.environ['GITHUB_TOKEN'] = open(os.path.join(os.environ['PENV'], 'github-token')).read().strip().partition(':')[2]
|
||||
run('git', 'tag', '-a', 'v' + VERSION, '-m', f'version {VERSION}')
|
||||
run('git', 'push')
|
||||
run('goreleaser', 'release', '--clean')
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
Reference in New Issue
Block a user