slog: fix SetDefault deadlock

If the program does

    SetDefault(Default())

with the original default logger, we end up with deadlock as
the call to log.Output invokes handlerWriter.Write, which
then calls log.Output again while the first invocation holds
the log.Logger mutex.

Fix by not installing a handleWriter if SetDefault's argument has
a defaultHandler.

Change-Id: I0db8a10f313d0e9913a65a75e163d7024ec96589
Reviewed-on: https://go-review.googlesource.com/c/exp/+/439058
Reviewed-by: Alan Donovan <adonovan@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
diff --git a/slog/logger.go b/slog/logger.go
index 13fffb5..8ac87a6 100644
--- a/slog/logger.go
+++ b/slog/logger.go
@@ -26,8 +26,16 @@
 // (as with [log.Print], etc.) will be logged at InfoLevel using l's Handler.
 func SetDefault(l *Logger) {
 	defaultLogger.Store(l)
-	log.SetOutput(&handlerWriter{l.Handler(), log.Flags()})
-	log.SetFlags(0) // we want just the log message, no time or location
+	// If the default's handler is a defaultHandler, then don't use a handleWriter,
+	// or we'll deadlock as they both try to acquire the log default mutex.
+	// The defaultHandler will use whatever the log default writer is currently
+	// set to, which is correct.
+	// This can occur with SetDefault(Default()).
+	// See TestSetDefault.
+	if _, ok := l.Handler().(*defaultHandler); !ok {
+		log.SetOutput(&handlerWriter{l.Handler(), log.Flags()})
+		log.SetFlags(0) // we want just the log message, no time or location
+	}
 }
 
 // handlerWriter is an io.Writer that calls a Handler.
diff --git a/slog/logger_test.go b/slog/logger_test.go
index 48cd0bd..ddcf4fa 100644
--- a/slog/logger_test.go
+++ b/slog/logger_test.go
@@ -6,6 +6,7 @@
 
 import (
 	"bytes"
+	"context"
 	"io"
 	"log"
 	"path/filepath"
@@ -353,3 +354,21 @@
 		}
 	})
 }
+
+func TestSetDefault(t *testing.T) {
+	// Verify that setting the default to itself does not result in deadlock.
+	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
+	defer cancel()
+	defer func(w io.Writer) { log.SetOutput(w) }(log.Writer())
+	log.SetOutput(io.Discard)
+	go func() {
+		Info("A")
+		SetDefault(Default())
+		Info("B")
+		cancel()
+	}()
+	<-ctx.Done()
+	if err := ctx.Err(); err != context.Canceled {
+		t.Errorf("wanted canceled, got %v", err)
+	}
+}