Skip to content

Commit

Permalink
Detach documents when client is deactivated (#1036)
Browse files Browse the repository at this point in the history
This commit implements the detachment of documents attached to clients
upon client deactivation. Previously, the status of the document was
only changed to 'detach' within DB.clients.ClientDocInfo.Status,
leading to issues where the deactivated client's presence remained
active and presence change events were not communicated to other
clients.

With this change, on client deactivation, the document's packs.PushPull
method is called directly to execute the complete detachment process.

Moreover, since the Yorkie cluster sharding mechanism assigns specific
documents to specific servers, the server managing client deactivation
may not always be the one handling the document. Therefore, in such
cases, a request will be made to the server currently responsible for
the document to detach it properly. To facilitate this communication
across the cluster, a new ClusterService has been added.

Follow-up tasks that need to be addressed after this PR include:

- Implementing authentication for cluster communication and preventing
  external exposure.
- Eliminating redundant database resource requests between
  clients.Deactivate and server.DetachDocument.
- Improving the p.Clear ChangePack creation logic.
- Introducing a connection pool for clusterClient.

---------

Co-authored-by: raararaara <[email protected]>
  • Loading branch information
hackerwins and raararaara committed Oct 18, 2024
1 parent 9513091 commit d1b173b
Show file tree
Hide file tree
Showing 36 changed files with 2,236 additions and 896 deletions.
4 changes: 3 additions & 1 deletion admin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type Client struct {
client v1connect.AdminServiceClient
authInterceptor *AuthInterceptor
logger *zap.Logger
isInsecure bool
}

// New creates an instance of Client.
Expand Down Expand Up @@ -100,6 +101,7 @@ func New(opts ...Option) (*Client, error) {
conn: conn,
logger: logger,
authInterceptor: NewAuthInterceptor(options.Token),
isInsecure: options.IsInsecure,
}, nil
}

Expand All @@ -120,7 +122,7 @@ func Dial(rpcAddr string, opts ...Option) (*Client, error) {
// Dial dials to the admin service.
func (c *Client) Dial(rpcAddr string) error {
if !strings.Contains(rpcAddr, "://") {
if c.conn.Transport == nil {
if c.isInsecure {
rpcAddr = "http://" + rpcAddr
} else {
rpcAddr = "https://" + rpcAddr
Expand Down
603 changes: 301 additions & 302 deletions api/docs/yorkie/v1/admin.openapi.yaml

Large diffs are not rendered by default.

309 changes: 309 additions & 0 deletions api/docs/yorkie/v1/cluster.openapi.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,309 @@
openapi: 3.1.0
info:
description: Yorkie is an open source document store for building collaborative
editing applications.
title: Yorkie
version: v0.5.1
servers:
- description: Production server
url: https://api.yorkie.dev
- description: Local server
url: http://localhost:8080
paths:
/yorkie.v1.ClusterService/DetachDocument:
post:
description: ""
requestBody:
$ref: '#/components/requestBodies/yorkie.v1.ClusterService.DetachDocument.yorkie.v1.ClusterServiceDetachDocumentRequest'
responses:
"200":
$ref: '#/components/responses/yorkie.v1.ClusterService.DetachDocument.yorkie.v1.ClusterServiceDetachDocumentResponse'
default:
$ref: '#/components/responses/connect.error'
tags:
- yorkie.v1.ClusterService
components:
requestBodies:
yorkie.v1.ClusterService.DetachDocument.yorkie.v1.ClusterServiceDetachDocumentRequest:
content:
application/json:
schema:
$ref: '#/components/schemas/yorkie.v1.ClusterServiceDetachDocumentRequest'
application/proto:
schema:
$ref: '#/components/schemas/yorkie.v1.ClusterServiceDetachDocumentRequest'
required: true
responses:
connect.error:
content:
application/json:
schema:
$ref: '#/components/schemas/connect.error'
application/proto:
schema:
$ref: '#/components/schemas/connect.error'
description: ""
yorkie.v1.ClusterService.DetachDocument.yorkie.v1.ClusterServiceDetachDocumentResponse:
content:
application/json:
schema:
$ref: '#/components/schemas/yorkie.v1.ClusterServiceDetachDocumentResponse'
application/proto:
schema:
$ref: '#/components/schemas/yorkie.v1.ClusterServiceDetachDocumentResponse'
description: ""
schemas:
connect.error:
additionalProperties: false
description: 'Error type returned by Connect: https://connectrpc.com/docs/go/errors/#http-representation'
properties:
code:
enum:
- CodeCanceled
- CodeUnknown
- CodeInvalidArgument
- CodeDeadlineExceeded
- CodeNotFound
- CodeAlreadyExists
- CodePermissionDenied
- CodeResourceExhausted
- CodeFailedPrecondition
- CodeAborted
- CodeOutOfRange
- CodeInternal
- CodeUnavailable
- CodeDataLoss
- CodeUnauthenticated
examples:
- CodeNotFound
type: string
message:
type: string
title: Connect Error
type: object
google.protobuf.Timestamp:
additionalProperties: false
description: |-
A Timestamp represents a point in time independent of any time zone or local
calendar, encoded as a count of seconds and fractions of seconds at
nanosecond resolution. The count is relative to an epoch at UTC midnight on
January 1, 1970, in the proleptic Gregorian calendar which extends the
Gregorian calendar backwards to year one.
All minutes are 60 seconds long. Leap seconds are "smeared" so that no leap
second table is needed for interpretation, using a [24-hour linear
smear](https://developers.google.com/time/smear).
The range is from 0001-01-01T00:00:00Z to 9999-12-31T23:59:59.999999999Z. By
restricting to that range, we ensure that we can convert to and from [RFC
3339](https://www.ietf.org/rfc/rfc3339.txt) date strings.
# Examples
Example 1: Compute Timestamp from POSIX `time()`.
Timestamp timestamp;
timestamp.set_seconds(time(NULL));
timestamp.set_nanos(0);
Example 2: Compute Timestamp from POSIX `gettimeofday()`.
struct timeval tv;
gettimeofday(&tv, NULL);
Timestamp timestamp;
timestamp.set_seconds(tv.tv_sec);
timestamp.set_nanos(tv.tv_usec * 1000);
Example 3: Compute Timestamp from Win32 `GetSystemTimeAsFileTime()`.
FILETIME ft;
GetSystemTimeAsFileTime(&ft);
UINT64 ticks = (((UINT64)ft.dwHighDateTime) << 32) | ft.dwLowDateTime;
// A Windows tick is 100 nanoseconds. Windows epoch 1601-01-01T00:00:00Z
// is 11644473600 seconds before Unix epoch 1970-01-01T00:00:00Z.
Timestamp timestamp;
timestamp.set_seconds((INT64) ((ticks / 10000000) - 11644473600LL));
timestamp.set_nanos((INT32) ((ticks % 10000000) * 100));
Example 4: Compute Timestamp from Java `System.currentTimeMillis()`.
long millis = System.currentTimeMillis();
Timestamp timestamp = Timestamp.newBuilder().setSeconds(millis / 1000)
.setNanos((int) ((millis % 1000) * 1000000)).build();
Example 5: Compute Timestamp from Java `Instant.now()`.
Instant now = Instant.now();
Timestamp timestamp =
Timestamp.newBuilder().setSeconds(now.getEpochSecond())
.setNanos(now.getNano()).build();
Example 6: Compute Timestamp from current time in Python.
timestamp = Timestamp()
timestamp.GetCurrentTime()
# JSON Mapping
In JSON format, the Timestamp type is encoded as a string in the
[RFC 3339](https://www.ietf.org/rfc/rfc3339.txt) format. That is, the
format is "{year}-{month}-{day}T{hour}:{min}:{sec}[.{frac_sec}]Z"
where {year} is always expressed using four digits while {month}, {day},
{hour}, {min}, and {sec} are zero-padded to two digits each. The fractional
seconds, which can go up to 9 digits (i.e. up to 1 nanosecond resolution),
are optional. The "Z" suffix indicates the timezone ("UTC"); the timezone
is required. A proto3 JSON serializer should always use UTC (as indicated by
"Z") when printing the Timestamp type and a proto3 JSON parser should be
able to accept both UTC and other timezones (as indicated by an offset).
For example, "2017-01-15T01:30:15.01Z" encodes 15.01 seconds past
01:30 UTC on January 15, 2017.
In JavaScript, one can convert a Date object to this format using the
standard
[toISOString()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString)
method. In Python, a standard `datetime.datetime` object can be converted
to this format using
[`strftime`](https://docs.python.org/2/library/time.html#time.strftime) with
the time format spec '%Y-%m-%dT%H:%M:%S.%fZ'. Likewise, in Java, one can use
the Joda Time's [`ISODateTimeFormat.dateTime()`](
http://joda-time.sourceforge.net/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTime()
) to obtain a formatter capable of generating timestamps in this format.
format: date-time
type: string
yorkie.v1.ClusterServiceDetachDocumentRequest:
additionalProperties: false
description: ""
properties:
clientId:
additionalProperties: false
description: ""
title: client_id
type: string
documentSummary:
$ref: '#/components/schemas/yorkie.v1.DocumentSummary'
additionalProperties: false
description: ""
title: document_summary
type: object
project:
$ref: '#/components/schemas/yorkie.v1.Project'
additionalProperties: false
description: ""
title: project
type: object
title: ClusterServiceDetachDocumentRequest
type: object
yorkie.v1.ClusterServiceDetachDocumentResponse:
additionalProperties: false
description: ""
title: ClusterServiceDetachDocumentResponse
type: object
yorkie.v1.DocumentSummary:
additionalProperties: false
description: ""
properties:
accessedAt:
$ref: '#/components/schemas/google.protobuf.Timestamp'
additionalProperties: false
description: ""
title: accessed_at
type: object
createdAt:
$ref: '#/components/schemas/google.protobuf.Timestamp'
additionalProperties: false
description: ""
title: created_at
type: object
id:
additionalProperties: false
description: ""
title: id
type: string
key:
additionalProperties: false
description: ""
title: key
type: string
snapshot:
additionalProperties: false
description: ""
title: snapshot
type: string
updatedAt:
$ref: '#/components/schemas/google.protobuf.Timestamp'
additionalProperties: false
description: ""
title: updated_at
type: object
title: DocumentSummary
type: object
yorkie.v1.Project:
additionalProperties: false
description: ""
properties:
authWebhookMethods:
additionalProperties: false
description: ""
items:
type: string
title: auth_webhook_methods
type: array
authWebhookUrl:
additionalProperties: false
description: ""
title: auth_webhook_url
type: string
clientDeactivateThreshold:
additionalProperties: false
description: ""
title: client_deactivate_threshold
type: string
createdAt:
$ref: '#/components/schemas/google.protobuf.Timestamp'
additionalProperties: false
description: ""
title: created_at
type: object
id:
additionalProperties: false
description: ""
title: id
type: string
name:
additionalProperties: false
description: ""
title: name
type: string
publicKey:
additionalProperties: false
description: ""
title: public_key
type: string
secretKey:
additionalProperties: false
description: ""
title: secret_key
type: string
updatedAt:
$ref: '#/components/schemas/google.protobuf.Timestamp'
additionalProperties: false
description: ""
title: updated_at
type: object
title: Project
type: object
securitySchemes:
ApiKeyAuth:
in: header
name: Authorization
type: apiKey
security:
- ApiKeyAuth: []
tags:
- description: ClusterService is a service that provides an API for Cluster.
name: yorkie.v1.ClusterService
Loading

0 comments on commit d1b173b

Please sign in to comment.