{"record":{"id":"470b36b79b9b4e9c","repo":"geektutu/7days-golang","slug":"panic-p-470b36","errorCode":null,"errorMessage":"panic(p)","messagePattern":"panic\\(p\\)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"gee-orm/day7-migrate/geeorm.go","lineNumber":69,"sourceCode":"// NewSession creates a new session for next operations\nfunc (engine *Engine) NewSession() *session.Session {\n\treturn session.New(engine.db, engine.dialect)\n}\n\n// TxFunc will be called between tx.Begin() and tx.Commit()\n// https://stackoverflow.com/questions/16184238/database-sql-tx-detecting-commit-or-rollback\ntype TxFunc func(*session.Session) (interface{}, error)\n\n// Transaction executes sql wrapped in a transaction, then automatically commit if no error occurs\nfunc (engine *Engine) Transaction(f TxFunc) (result interface{}, err error) {\n\ts := engine.NewSession()\n\tif err := s.Begin(); err != nil {\n\t\treturn nil, err\n\t}\n\tdefer func() {\n\t\tif p := recover(); p != nil {\n\t\t\t_ = s.Rollback()\n\t\t\tpanic(p) // re-throw panic after Rollback\n\t\t} else if err != nil {\n\t\t\t_ = s.Rollback() // err is non-nil; don't change it\n\t\t} else {\n\t\t\terr = s.Commit() // err is nil; if Commit returns error update err\n\t\t}\n\t}()\n\n\treturn f(s)\n}\n\n// difference returns a - b\nfunc difference(a []string, b []string) (diff []string) {\n\tmapB := make(map[string]bool)\n\tfor _, v := range b {\n\t\tmapB[v] = true\n\t}\n\tfor _, v := range a {\n\t\tif _, ok := mapB[v]; !ok {","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/geektutu/7days-golang/blob/cf3644382101dc13e7fd92e8f5c66cabc51bcd3b/gee-orm/day7-migrate/geeorm.go#L51-L87","documentation":"This is not an error value but a re-panicked Go panic. In gee-orm, when code inside a transaction panics, the deferred recovery block first rolls the transaction back, then re-throws the original panic with panic(p) so the failure is not silently swallowed. You see it because your code (or the library's callback) panicked mid-transaction and the transaction was already rolled back before the panic propagated.","triggerScenarios":"Any panic raised between geeorm.Transaction's Begin() and Commit()/Rollback() — e.g. a nil pointer dereference, index out of range, or an explicit panic inside the transaction callback/function passed to s (Session) operations.","commonSituations":"A nil map or nil pointer touched inside a transaction body; a user-supplied hook/OnUpdate callback that panics; unexpected type assertion failures on scanned columns during a migration or query within the transaction.","solutions":["Find the ORIGINAL panic value: look at the stack trace above this re-throw to locate the actual panicking code, since the defer at geeorm.go:69 only re-throws after Rollback.","Fix the root cause in the code that ran inside the transaction (nil checks, bounds checks, safe type assertions).","If you intentionally panic inside a transaction, recover it outside the geeorm.Transaction call instead.","No data cleanup is needed: the library already called s.Rollback(), so the DB transaction was aborted correctly."],"exampleFix":"// before: code inside transaction panics on nil map\nfunc (u *UserMgr) Save(s *geeorm.Session) {\n    u.settings[\"a\"] = 1 // panics if u.settings is nil\n}\n// after: initialize before use\nfunc (u *UserMgr) Save(s *geeorm.Session) {\n    if u.settings == nil {\n        u.settings = map[string]interface{}{}\n    }\n    u.settings[\"a\"] = 1\n}","handlingStrategy":"try-catch","validationCode":"// Go: validate transaction inputs before entering geeorm.Transaction\nif tx == nil || db == nil {\n    return fmt.Errorf(\"transaction requires non-nil db\")\n}\nif err := validateRows(rows); err != nil {\n    return err\n}","typeGuard":"func nonNil[T any](v T, ok bool) bool { return ok && !isNil(v) }\n// usage: if u.settings == nil { u.settings = map[string]any{} }","tryCatchPattern":"// Go: recover around the transaction boundary so panics inside don't escape\nfunc safeTx(db *geeorm.DB, fn func(s *geeorm.Session) error) (err error) {\n    defer func() {\n        if p := recover(); p != nil {\n            err = fmt.Errorf(\"transaction panicked: %v\", p)\n        }\n    }()\n    _, err = db.Transaction(fn)\n    return err\n}","preventionTips":["Never call panic() for expected error paths inside transactions; return errors instead.","Initialize all maps/slices used inside transaction callbacks before calling geeorm.Transaction.","Log recover()'d panic values at the boundary so the root cause is visible above the re-throw.","Remember the library already rolls back on panic — don't add a second Rollback that can double-fire."],"tags":["go","orm","panic","transaction","rollback"],"backgroundTag":"panic-in-transaction-rollback","analyzedSha":"cf3644382101dc13e7fd92e8f5c66cabc51bcd3b","analyzedAt":"2026-09-03T18:31:24.087Z","contentChangedAt":"2026-09-03T18:31:24.087Z","schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}