geektutu/7days-golang · critical

panic(p)

Error message

panic(p)

What it means

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.

Source

Thrown at gee-orm/day7-migrate/geeorm.go:69

// NewSession creates a new session for next operations
func (engine *Engine) NewSession() *session.Session {
	return session.New(engine.db, engine.dialect)
}

// TxFunc will be called between tx.Begin() and tx.Commit()
// https://stackoverflow.com/questions/16184238/database-sql-tx-detecting-commit-or-rollback
type TxFunc func(*session.Session) (interface{}, error)

// Transaction executes sql wrapped in a transaction, then automatically commit if no error occurs
func (engine *Engine) Transaction(f TxFunc) (result interface{}, err error) {
	s := engine.NewSession()
	if err := s.Begin(); err != nil {
		return nil, err
	}
	defer func() {
		if p := recover(); p != nil {
			_ = s.Rollback()
			panic(p) // re-throw panic after Rollback
		} else if err != nil {
			_ = s.Rollback() // err is non-nil; don't change it
		} else {
			err = s.Commit() // err is nil; if Commit returns error update err
		}
	}()

	return f(s)
}

// difference returns a - b
func difference(a []string, b []string) (diff []string) {
	mapB := make(map[string]bool)
	for _, v := range b {
		mapB[v] = true
	}
	for _, v := range a {
		if _, ok := mapB[v]; !ok {

View on GitHub (pinned to cf36443821)

Solutions

  1. 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.
  2. Fix the root cause in the code that ran inside the transaction (nil checks, bounds checks, safe type assertions).
  3. If you intentionally panic inside a transaction, recover it outside the geeorm.Transaction call instead.
  4. No data cleanup is needed: the library already called s.Rollback(), so the DB transaction was aborted correctly.

Example fix

// before: code inside transaction panics on nil map
func (u *UserMgr) Save(s *geeorm.Session) {
    u.settings["a"] = 1 // panics if u.settings is nil
}
// after: initialize before use
func (u *UserMgr) Save(s *geeorm.Session) {
    if u.settings == nil {
        u.settings = map[string]interface{}{}
    }
    u.settings["a"] = 1
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Go: validate transaction inputs before entering geeorm.Transaction
if tx == nil || db == nil {
    return fmt.Errorf("transaction requires non-nil db")
}
if err := validateRows(rows); err != nil {
    return err
}

Type guard

func nonNil[T any](v T, ok bool) bool { return ok && !isNil(v) }
// usage: if u.settings == nil { u.settings = map[string]any{} }

Try / catch

// Go: recover around the transaction boundary so panics inside don't escape
func safeTx(db *geeorm.DB, fn func(s *geeorm.Session) error) (err error) {
    defer func() {
        if p := recover(); p != nil {
            err = fmt.Errorf("transaction panicked: %v", p)
        }
    }()
    _, err = db.Transaction(fn)
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of geektutu/7days-golang@cf36443821 (2026-09-03). Data as JSON: /api/errors/470b36b79b9b4e9c. Report an issue: GitHub.