package main // Run with: // // On master this allocates 30+GB of memory and never finishes. // // go build -o reproducer reproducer.go && time ./reproducer // Now passing 1262568 bytes to ParseSchema() to demonstrate the benefit of the json marshaling change // Alloc = 4160 MiB TotalAlloc = 5630 MiB Sys = 5578 MiB NumGC = 18 // Alloc = 8312 MiB TotalAlloc = 11174 MiB Sys = 11120 MiB NumGC = 19 // Alloc = 19392 MiB TotalAlloc = 22254 MiB Sys = 22203 MiB NumGC = 19 // Alloc = 16616 MiB TotalAlloc = 22254 MiB Sys = 22203 MiB NumGC = 20 // Alloc = 16616 MiB TotalAlloc = 22254 MiB Sys = 22203 MiB NumGC = 20 // Alloc = 16616 MiB TotalAlloc = 22254 MiB Sys = 22203 MiB NumGC = 20 // Alloc = 33223 MiB TotalAlloc = 44405 MiB Sys = 44371 MiB NumGC = 21 // Alloc = 33223 MiB TotalAlloc = 44405 MiB Sys = 44371 MiB NumGC = 21 // Alloc = 33223 MiB TotalAlloc = 44405 MiB Sys = 44371 MiB NumGC = 21 // Alloc = 33223 MiB TotalAlloc = 44405 MiB Sys = 44371 MiB NumGC = 21 // Alloc = 33223 MiB TotalAlloc = 44405 MiB Sys = 44371 MiB NumGC = 21 // // With the position JSON annotation change this finishes in well under a second. // // go build -o reproducer reproducer.go && time ./reproducer // Now passing 1262568 bytes to ParseSchema() to demonstrate the benefit of the json marshaling change // Got 3705551 bytes of JSON // // real 0m0.380s // user 0m0.075s // sys 0m0.020s import ( "encoding/json" "fmt" "io" "net/http" "os" "runtime" "time" "github.com/vektah/gqlparser/v2/ast" "github.com/vektah/gqlparser/v2/parser" ) // Complex GQL schema to test with const gqlSchema = `https://docs.github.com/public/fpt/schema.docs.graphql` // Seconds between memory dump information const memDumpSeconds = 3 func main() { b, err := downloadSchema(gqlSchema) if err != nil { fmt.Fprintf(os.Stderr, "Error downloading: %s\n", err) } // Dump the memory usage every few seconds ticker := time.NewTicker(memDumpSeconds * time.Second) done := make(chan bool) go func() { for { select { case <-ticker.C: PrintMemUsage() case <-done: ticker.Stop() return } } }() // This should allocate a bunch of memory and not return fmt.Fprintf(os.Stderr, "Now passing %d bytes to ParseSchema() to demonstrate the benefit of the json marshaling change\n", len(b)) // Parse schema doc, err := parser.ParseSchema(&ast.Source{Input: string(b), Name: "github.graphql"}) if err != nil { panic(err) } // Marshal to JSON s, err := json.Marshal(doc) if err != nil { panic(err) } fmt.Fprintf(os.Stderr, "Got %d bytes of JSON\n", len(s)) // Stop printing memory usage done <- true } func downloadSchema(url string) ([]byte, error) { response, err := http.Get(url) if err != nil { fmt.Fprintf(os.Stderr, "Error while downloading %s - %s\n", url, err) return []byte{}, err } defer response.Body.Close() return io.ReadAll(response.Body) } // From: https://gist.github.com/j33ty/79e8b736141be19687f565ea4c6f4226 // PrintMemUsage outputs the current, total and OS memory being used. As well as the number // of garage collection cycles completed. func PrintMemUsage() { var m runtime.MemStats runtime.ReadMemStats(&m) // For info on each, see: https://golang.org/pkg/runtime/#MemStats fmt.Printf("Alloc = %v MiB", bToMb(m.Alloc)) fmt.Printf("\tTotalAlloc = %v MiB", bToMb(m.TotalAlloc)) fmt.Printf("\tSys = %v MiB", bToMb(m.Sys)) fmt.Printf("\tNumGC = %v\n", m.NumGC) } func bToMb(b uint64) uint64 { return b / 1024 / 1024 }