xpc.go 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899
  1. package xpc
  2. import (
  3. "context"
  4. "fmt"
  5. "os"
  6. "os/signal"
  7. "reflect"
  8. "syscall"
  9. "time"
  10. )
  11. // Implements is a struct that component implementations must embed.
  12. // T is the component interface.
  13. type Implements[T any] struct{}
  14. // Register is used by generated code to register components.
  15. func Register[Intf any, Impl any](newFunc func() any) {
  16. registerComponent((*Intf)(nil), (*Impl)(nil), newFunc)
  17. }
  18. // Options configuration for the XPC runtime
  19. type Options struct {
  20. // TLSCertFile path to the TLS certificate file (for cluster mode)
  21. TLSCertFile string
  22. // TLSKeyFile path to the TLS key file (for cluster mode)
  23. TLSKeyFile string
  24. }
  25. // Run starts the XPC application.
  26. // app must be a function with the signature: func(context.Context, Component...) error
  27. // It handles OS signals (SIGINT, SIGTERM) to cancel the context passed to the app.
  28. func Run(ctx context.Context, app any, opts ...Options) error {
  29. // Parse options
  30. var opt Options
  31. if len(opts) > 0 {
  32. opt = opts[0]
  33. }
  34. rtConfig := runtimeConfig{
  35. TLSCertFile: opt.TLSCertFile,
  36. TLSKeyFile: opt.TLSKeyFile,
  37. }
  38. appVal := reflect.ValueOf(app)
  39. appType := appVal.Type()
  40. if appType.Kind() != reflect.Func {
  41. return fmt.Errorf("app must be a function, got %v", appType)
  42. }
  43. if appType.NumIn() < 1 || appType.In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() {
  44. return fmt.Errorf("first argument of app must be context.Context")
  45. }
  46. if appType.NumOut() != 1 || appType.Out(0) != reflect.TypeOf((*error)(nil)).Elem() {
  47. return fmt.Errorf("app must return a single error")
  48. }
  49. // Setup signal handling
  50. ctx, cancel := signal.NotifyContext(ctx, os.Interrupt, syscall.SIGTERM)
  51. defer cancel()
  52. // Initialize Runtime based on configuration
  53. var rt runtime
  54. if os.Getenv("XPC_MODE") == "cluster" {
  55. rt = newClusterRuntime(rtConfig)
  56. } else {
  57. rt = newLocalRuntime(rtConfig)
  58. }
  59. // Instantiate requested root components
  60. args := []reflect.Value{reflect.ValueOf(ctx)}
  61. for i := 1; i < appType.NumIn(); i++ {
  62. argType := appType.In(i)
  63. comp, err := rt.getInstance(ctx, argType)
  64. if err != nil {
  65. return fmt.Errorf("failed to get instance for argument %d (%v): %w", i, argType, err)
  66. }
  67. args = append(args, reflect.ValueOf(comp))
  68. }
  69. // Execute Application
  70. results := appVal.Call(args)
  71. // Graceful Shutdown
  72. shutdownCtx, shutdownCancel := context.WithTimeout(context.Background(), 10*time.Second)
  73. defer shutdownCancel()
  74. if err := rt.shutdown(shutdownCtx); err != nil {
  75. fmt.Fprintf(os.Stderr, "xpc: shutdown error: %v\n", err)
  76. }
  77. errVal := results[0]
  78. if !errVal.IsNil() {
  79. return errVal.Interface().(error)
  80. }
  81. return nil
  82. }