Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix Child Process not working for BGREWRITEAOF on MacOS + Logging Improvements #704

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions internal/eval/eval_darwin_arm64.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,20 @@ func EvalBGREWRITEAOF(args []string, store *dstore.Store) []byte {
if config.EnableMultiThreading {
return nil
}
pid, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)

// Get the original PID (Process ID) - This is needed to check if we are in child process or not.
// The document approach is to check the return value of fork (it's 0 for child and non-zero for parent) but this is more reliable.
// For more details check - https://github.com/DiceDB/dice/issues/683
originalPID := syscall.Getpid()
_, _, err := syscall.RawSyscall(syscall.SYS_FORK, 0, 0, 0)

if err != 0 {
return diceerrors.NewErrWithMessage("Fork failed")
}

if pid == 0 {
isChild := syscall.Getppid() == originalPID

if isChild {
// We are inside child process now, so we'll start flushing to disk.
if err := dstore.DumpAllAOF(store); err != nil {
return diceerrors.NewErrWithMessage("AOF failed")
Expand Down
105 changes: 69 additions & 36 deletions internal/logger/zerolog.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package logger
import (
"context"
"log/slog"
"time"

"github.com/rs/zerolog"
)
Expand All @@ -24,58 +25,26 @@ func newZerologHandler(logger *zerolog.Logger) *ZerologHandler {
//nolint:gocritic // The slog.Record struct triggers hugeParam but we don't control the interface (it's a standard library one)
func (h *ZerologHandler) Handle(ctx context.Context, record slog.Record) error {
event := h.logger.WithLevel(mapLevel(record.Level))

record.Attrs(func(attr slog.Attr) bool {
switch attr.Value.Kind() {
case slog.KindString:
event = event.Str(attr.Key, attr.Value.String())
case slog.KindInt64:
event = event.Int64(attr.Key, attr.Value.Int64())
case slog.KindFloat64:
event = event.Float64(attr.Key, attr.Value.Float64())
case slog.KindBool:
event = event.Bool(attr.Key, attr.Value.Bool())
default:
switch v := attr.Value.Any().(type) {
// error is a special case since zerlog has a dedicated method for it but it's not part of the slog.Kind
case error:
event = event.Err(v)
default:
event = event.Interface(attr.Key, attr.Value.Any())
}
}
addAttrToZerolog(attr, event)
return true
})

event.Msg(record.Message)
return nil
}

// mapLevel maps slog levels to zerolog levels
func mapLevel(level slog.Level) zerolog.Level {
switch {
case level == slog.LevelDebug:
return zerolog.DebugLevel
case level == slog.LevelWarn:
return zerolog.WarnLevel
case level == slog.LevelError:
return zerolog.ErrorLevel
default:
return zerolog.InfoLevel
}
}

// Enabled implements the slog.Handler interface
func (h *ZerologHandler) Enabled(ctx context.Context, level slog.Level) bool {
return h.logger.GetLevel() <= mapLevel(level)
}

// WithAttrs adds attributes to the log event
func (h *ZerologHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
logger := *h.logger
ctx := h.logger.With()
for _, attr := range attrs {
logger = logger.With().Interface(attr.Key, attr.Value).Logger()
ctx = addAttrToZerolog(attr, ctx)
}
logger := ctx.Logger()
return newZerologHandler(&logger)
}

Expand All @@ -84,3 +53,67 @@ func (h *ZerologHandler) WithGroup(name string) slog.Handler {
logger := h.logger.With().Str("group", name).Logger()
return newZerologHandler(&logger)
}

// addAttrToZerolog is a generic function to add an slog.Attr to either a zerolog.Event or zerolog.Context
func addAttrToZerolog[T interface {
Str(string, string) T
Int64(string, int64) T
Uint64(string, uint64) T
Float64(string, float64) T
Bool(string, bool) T
Err(error) T
Time(string, time.Time) T
Dur(string, time.Duration) T
Interface(string, any) T
AnErr(key string, err error) T
}](attr slog.Attr, target T) T {
switch attr.Value.Kind() {
case slog.KindBool:
return target.Bool(attr.Key, attr.Value.Bool())
case slog.KindDuration:
return target.Dur(attr.Key, attr.Value.Duration())
case slog.KindFloat64:
return target.Float64(attr.Key, attr.Value.Float64())
case slog.KindInt64:
return target.Int64(attr.Key, attr.Value.Int64())
case slog.KindString:
return target.Str(attr.Key, attr.Value.String())
case slog.KindTime:
return target.Time(attr.Key, attr.Value.Time())
case slog.KindUint64:
return target.Uint64(attr.Key, attr.Value.Uint64())
case slog.KindGroup:
// For group, we need to recurse into the group's attributes
group := attr.Value.Group()
for _, groupAttr := range group {
target = addAttrToZerolog(groupAttr, target)
}
return target
case slog.KindLogValuer:
// LogValuer is a special case that needs to be resolved
resolved := attr.Value.Resolve()
return addAttrToZerolog(slog.Attr{Key: attr.Key, Value: resolved}, target)
default:
switch v := attr.Value.Any().(type) {
// error is a special case since zerlog has a dedicated method for it but it's not part of the slog.Kind
case error:
return target.AnErr(attr.Key, v)
default:
return target.Interface(attr.Key, attr.Value)
}
}
}

// mapLevel maps slog levels to zerolog levels
func mapLevel(level slog.Level) zerolog.Level {
switch {
case level == slog.LevelDebug:
return zerolog.DebugLevel
case level == slog.LevelWarn:
return zerolog.WarnLevel
case level == slog.LevelError:
return zerolog.ErrorLevel
default:
return zerolog.InfoLevel
}
}