Skip to content

Commit e8970b8

Browse files
committed
Implement shutdown use case
1 parent d6b8fa2 commit e8970b8

17 files changed

Lines changed: 280 additions & 71 deletions

File tree

client.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,7 @@ package appsync
22

33
import (
44
"context"
5-
"errors"
65

7-
"github.com/exanubes/appsync/internal/app/engine"
8-
"github.com/exanubes/appsync/internal/app/services/connection"
96
"github.com/exanubes/appsync/internal/app/usecases/publish"
107
"github.com/exanubes/appsync/internal/app/usecases/subscribe"
118
"github.com/exanubes/appsync/internal/composition"
@@ -17,9 +14,7 @@ const (
1714
)
1815

1916
type appsync_client struct {
20-
transport connection.Connection
21-
runtime *engine.Engine
22-
usecases *composition.UseCases
17+
usecases *composition.UseCases
2318
}
2419

2520
func (client *appsync_client) Publish(ctx context.Context, input PublishCommandInput) error {
@@ -50,5 +45,5 @@ func (client *appsync_client) Subscribe(ctx context.Context, input SubscribeComm
5045
}
5146

5247
func (client *appsync_client) Close(ctx context.Context) error {
53-
return errors.Join(client.runtime.Close(ctx), client.transport.Close())
48+
return client.usecases.Shutdown.Execute(ctx)
5449
}

connect.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"net/url"
77

8+
"github.com/exanubes/appsync/authorizer"
89
"github.com/exanubes/appsync/internal/app"
910
"github.com/exanubes/appsync/internal/app/engine"
1011
"github.com/exanubes/appsync/internal/app/heartbeat"
@@ -14,8 +15,8 @@ import (
1415
"github.com/exanubes/appsync/internal/app/runtime"
1516
"github.com/exanubes/appsync/internal/app/services/connection"
1617
"github.com/exanubes/appsync/internal/app/services/io"
18+
"github.com/exanubes/appsync/internal/app/usecases/shutdown"
1719
"github.com/exanubes/appsync/internal/composition"
18-
"github.com/exanubes/appsync/authorizer"
1920
infra_authorizer "github.com/exanubes/appsync/internal/infrastructure/authorizer"
2021
"github.com/exanubes/appsync/internal/infrastructure/clock"
2122
"github.com/exanubes/appsync/internal/infrastructure/codec"
@@ -138,24 +139,32 @@ func (builder *builder) Connect(ctx context.Context) (*appsync_client, error) {
138139
egress_queue := queue.NewEgressQueue(builder.backpressure.ConnectionOutbound)
139140
pending_registry := pending.NewRegistry()
140141
io_loops := io.New(ingress_queue, egress_queue, connection_output.Connection, msg_codec)
141-
usecases := composition.NewUseCases(
142+
usecases, services := composition.NewUseCases(
142143
request_authorizer,
143144
ingress_queue,
144145
egress_queue,
145146
pending_registry,
146147
builder.backpressure.SubscriptionEvents,
147148
)
149+
148150
msg_router := router.New(pending_registry, usecases.ReceiveData)
149151
runtime := runtime.New(ingress_queue, msg_router, heartbeat)
150152
session := engine.New(heartbeat, runtime, io_loops, builder.logger)
151153
session.Start(ctx, engine.StartEngineInput{
152154
Timeout: connection_output.Timeout,
153155
})
154156

157+
shutdown_connection_usecase := shutdown.NewShutdownConnectionUsecase(
158+
services.SubscriptionsRegistry,
159+
services.RemoveSubscriptions,
160+
session,
161+
connection_output.Connection,
162+
)
163+
164+
usecases.Shutdown = shutdown_connection_usecase
165+
155166
return &appsync_client{
156-
transport: connection_output.Connection,
157-
runtime: session,
158-
usecases: usecases,
167+
usecases: usecases,
159168
}, nil
160169
}
161170

internal/app/engine/engine.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package engine
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"sync"
87

98
"github.com/exanubes/appsync/internal/app"
@@ -78,7 +77,7 @@ func (engine *Engine) Close(ctx context.Context) error {
7877

7978
case err := <-engine.err_channel:
8079
if err != nil && !errors.Is(err, context.Canceled) {
81-
error = fmt.Errorf("%w\n%w", error, err)
80+
error = errors.Join(err)
8281
}
8382
default:
8483
}

internal/app/services/connection/ports.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,17 @@ type Writer interface {
5050
}
5151

5252
type Closer interface {
53-
Close() error
53+
Close(context.Context) error
5454
}
5555

5656
type Serializer interface {
5757
Serialize(app.Signature) (string, error)
5858
}
59+
60+
type Runtime interface {
61+
Close(context.Context) error
62+
}
63+
64+
type Transport interface {
65+
Close(context.Context) error
66+
}

internal/app/services/subscription/create-subscription.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import (
55
)
66

77
type CreateSubscriptionService struct {
8-
registry Registry
8+
registry SubscribeRegistry
99
backpressure uint
1010
}
1111

12-
func NewCreateSubscriptionService(registry Registry, backpressure uint) *CreateSubscriptionService {
12+
func NewCreateSubscriptionService(registry SubscribeRegistry, backpressure uint) *CreateSubscriptionService {
1313
return &CreateSubscriptionService{
1414
registry: registry,
1515
backpressure: backpressure,
Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,35 @@
11
package subscription
22

33
import (
4+
"context"
5+
6+
"github.com/exanubes/appsync/internal/app"
47
"github.com/exanubes/appsync/internal/app/subscription"
58
)
69

7-
type Registry interface {
10+
type SubscribeRegistry interface {
811
Register(*subscription.Subscription)
912
}
1013

14+
type UnsubscribeRegistry interface {
15+
Remove(id string)
16+
Get(id string) *subscription.Subscription
17+
}
18+
1119
type CreateSubscriptionInput struct {
1220
ID string
1321
Channel string
1422
}
23+
24+
type Subscriptions interface {
25+
Active() []string
26+
Get(string) *subscription.Subscription
27+
}
28+
29+
type FrameFactory interface {
30+
Unsubscribe() app.FrameBuilder
31+
}
32+
33+
type Unsubscriber interface {
34+
Unsubscribe(ctx context.Context, subscription_id string) error
35+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package subscription
2+
3+
import (
4+
"context"
5+
"errors"
6+
"sync"
7+
)
8+
9+
type RemoveSubscriptionsService struct {
10+
subscription Unsubscriber
11+
}
12+
13+
func NewRemoveSubscriptionsService(unsubscribe Unsubscriber) *RemoveSubscriptionsService {
14+
return &RemoveSubscriptionsService{
15+
subscription: unsubscribe,
16+
}
17+
}
18+
19+
func (service *RemoveSubscriptionsService) Remove(ctx context.Context, subscription_ids ...string) error {
20+
var wg sync.WaitGroup
21+
var err error
22+
error_channel := make(chan error, len(subscription_ids))
23+
24+
for _, id := range subscription_ids {
25+
wg.Go(func() {
26+
select {
27+
case error_channel <- service.subscription.Unsubscribe(ctx, id):
28+
case <-ctx.Done():
29+
default:
30+
}
31+
})
32+
}
33+
wg.Wait()
34+
index := len(subscription_ids)
35+
for index > 0 {
36+
index -= 1
37+
select {
38+
case <-ctx.Done():
39+
return ctx.Err()
40+
case unsub_error := <-error_channel:
41+
err = errors.Join(err, unsub_error)
42+
default:
43+
}
44+
}
45+
46+
return err
47+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package subscription
2+
3+
import (
4+
"context"
5+
"github.com/exanubes/appsync/internal/app"
6+
"github.com/exanubes/appsync/internal/app/protocol"
7+
)
8+
9+
type UnsubscribeService struct {
10+
subscriptions UnsubscribeRegistry
11+
writer app.SendMessageService
12+
authorizer app.RequestAuthorizer
13+
frame FrameFactory
14+
}
15+
16+
func NewUnsubscribeService(
17+
subscriptions UnsubscribeRegistry,
18+
writer app.SendMessageService,
19+
authorizer app.RequestAuthorizer,
20+
frame FrameFactory,
21+
) *UnsubscribeService {
22+
return &UnsubscribeService{
23+
frame: frame,
24+
authorizer: authorizer,
25+
writer: writer,
26+
subscriptions: subscriptions,
27+
}
28+
}
29+
30+
func (service UnsubscribeService) Unsubscribe(ctx context.Context, subscription_id string) error {
31+
frame := service.frame.Unsubscribe()
32+
subscription := service.subscriptions.Get(subscription_id)
33+
34+
if subscription == nil {
35+
return app.ErrSubscriptionClosed
36+
}
37+
38+
if !subscription.Active() {
39+
return app.ErrSubscriptionClosed
40+
}
41+
42+
signature, err := service.authorizer.Authorize(ctx, app.AuthorizeCommandInput{})
43+
if err != nil {
44+
return err
45+
}
46+
47+
frame.WithType(protocol.TypeUnsubscribe).
48+
WithSignature(signature).
49+
WithID(subscription_id)
50+
51+
err = service.writer.Send(ctx, frame.Build())
52+
53+
if err != nil {
54+
return err
55+
}
56+
57+
subscription.Close()
58+
service.subscriptions.Remove(subscription_id)
59+
60+
return nil
61+
}

internal/app/subscription/registry.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,18 @@ func (registry *Registry) Get(id string) *Subscription {
3838

3939
return nil
4040
}
41+
42+
func (registry *Registry) Active() []string {
43+
registry.mutex.RLock()
44+
defer registry.mutex.RUnlock()
45+
46+
ids := []string{}
47+
48+
for id, sub := range registry.store {
49+
if sub.Active() {
50+
ids = append(ids, id)
51+
}
52+
}
53+
54+
return ids
55+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package shutdown
2+
3+
import "context"
4+
5+
type ShutdownConnection interface {
6+
Execute(context.Context) error
7+
}
8+
9+
type SubscriptionRegistry interface {
10+
Active() []string
11+
}
12+
13+
type Closer interface {
14+
Close(context.Context) error
15+
}
16+
17+
type Remover interface {
18+
Remove(context.Context, ...string) error
19+
}
20+
21+
type Runtime interface {
22+
Close(context.Context) error
23+
}
24+
25+
type Transport interface {
26+
Close(context.Context) error
27+
}

0 commit comments

Comments
 (0)