Skip to content

Commit 93e24a6

Browse files
jw3scholarsmate
andauthored
Fix leaking of unqualified viewport ids (#1007)
* Fix leaking of unqualified viewport ids There were two spots that were leaking unqualified viewport ids. 1. The EditorService was returning the vid when a viewport was unwatched. This is fixed by simply echoing back the oid that the request was submitted with. 2. The Session actor had more significant issues where it was returning an invalid session id, was also returning the internal viewport id, and then was searching for viewport actors with the qualified id. This should fix both issues and provides a unit test to validate them through the grpc service. Closes #976 * test cases updated to reproduce the problem reported in #976 --------- Co-authored-by: Davin Shearer <[email protected]>
1 parent be0e258 commit 93e24a6

File tree

6 files changed

+82
-8
lines changed

6 files changed

+82
-8
lines changed

packages/client/tests/specs/common.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,10 +171,13 @@ export async function subscribeViewport(
171171
: 1
172172
)
173173
const event = viewportEvent.getViewportEventKind()
174+
const viewport_id_from_event = viewportEvent.getViewportId()
174175
if (ViewportEventKind.VIEWPORT_EVT_EDIT == event) {
175176
log_info(
176177
'viewport_id: ' +
177178
viewport_id +
179+
', viewport_id_from_event: ' +
180+
viewport_id_from_event +
178181
', event: ' +
179182
event +
180183
', serial: ' +
@@ -188,6 +191,15 @@ export async function subscribeViewport(
188191
'", callbacks: ' +
189192
viewport_callbacks.get(viewport_id)
190193
)
194+
if (viewport_id_from_event !== viewport_id) {
195+
log_error(
196+
'viewport ID mismatch: "' +
197+
viewport_id +
198+
'" not equal to "' +
199+
viewport_id_from_event +
200+
'"'
201+
)
202+
}
191203
} else {
192204
log_info(
193205
'viewport: ' +

packages/client/tests/specs/viewport.spec.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -176,12 +176,15 @@ describe('Viewports', () => {
176176
await checkCallbackCount(viewport_callbacks, viewport_1_id, 1)
177177
await checkCallbackCount(viewport_callbacks, viewport_2_id, 3)
178178
log_info(viewport_callbacks)
179+
179180
// Unsubscribe all viewporta
180-
await unsubscribeViewport(viewport_1_id)
181-
await unsubscribeViewport(viewport_2_id)
181+
expect(await unsubscribeViewport(viewport_1_id)).to.equal(viewport_1_id)
182+
expect(await unsubscribeViewport(viewport_2_id)).to.equal(viewport_2_id)
183+
182184
// Note the viewports are not destroyed, just unsubscribed
183185
expect(await getViewportCount(session_id)).to.equal(1)
184-
await destroyViewport(viewport_1_id)
186+
187+
expect(await destroyViewport(viewport_1_id)).to.equal(viewport_1_id)
185188
expect(await getViewportCount(session_id)).to.equal(0)
186189
}).timeout(8000)
187190

@@ -304,6 +307,7 @@ describe('Viewports', () => {
304307
false
305308
)
306309
const viewport_id = viewport_response.getViewportId()
310+
expect(viewport_id).to.equal(session_id + ':' + desired_viewport_id)
307311
expect(await viewportHasChanges(viewport_id)).to.be.false
308312
expect(viewport_response.getData_asU8()).to.deep.equal(Buffer.from(''))
309313
expect(viewport_response.getLength()).to.equal(0)
@@ -414,7 +418,9 @@ describe('Viewports', () => {
414418
)
415419
expect(await getViewportCount(session_id)).to.equal(1)
416420
// Unsubscribe the viewport
417-
expect(await unsubscribeViewport(viewport_id)).to.equal(desired_viewport_id)
421+
expect(await unsubscribeViewport(viewport_id)).to.equal(
422+
session_id + ':' + desired_viewport_id
423+
)
418424
// Note the viewport is not destroyed, just unsubscribed
419425
expect(await getViewportCount(session_id)).to.equal(1)
420426
await destroyViewport(viewport_id)

server/scala/api/src/main/scala/com/ctc/omega_edit/ViewportImpl.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ private[omega_edit] class ViewportImpl(p: Pointer, i: FFI) extends Viewport {
6767

6868
def modify(offset: Long, capacity: Long, isFloating: Boolean): Boolean =
6969
i.omega_viewport_modify(p, offset, capacity, if (isFloating) 1 else 0) == 0
70+
7071
override def toString: String =
7172
data.mkString // TODO: probably render instead as hex
7273

server/scala/serv/src/main/scala/com/ctc/omega_edit/grpc/EditorService.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -444,7 +444,7 @@ class EditorService(implicit val system: ActorSystem) extends Editor {
444444
ObjectId(in.id) match {
445445
case Viewport.Id(sid, vid) =>
446446
(editors ? ViewportOp(sid, vid, Viewport.Unwatch)).mapTo[Result].map {
447-
case Ok(id) => ObjectId(id)
447+
case Ok(_) => in
448448
case Err(c) => throw grpcFailure(c)
449449
}
450450
case _ =>

server/scala/serv/src/main/scala/com/ctc/omega_edit/grpc/Session.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ class Session(
194194
val vid = id.getOrElse(Viewport.Id.uuid())
195195
val fqid = s"$sessionId:$vid"
196196

197-
context.child(fqid) match {
197+
context.child(vid) match {
198198
case Some(_) =>
199199
sender() ! Err(Status.ALREADY_EXISTS)
200200
case None =>
@@ -204,8 +204,8 @@ class Session(
204204
val cb = ViewportCallback { (v, e, c) =>
205205
input.queue.offer(
206206
ViewportEvent(
207-
sessionId = fqid,
208-
viewportId = vid,
207+
sessionId = sessionId,
208+
viewportId = fqid,
209209
serial = c.map(_.id),
210210
data = Option(ByteString.copyFrom(v.data)),
211211
length = Some(v.data.size.toLong),
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2024 Concurrent Technologies Corporation
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.ctc.omega_edit.grpc
18+
19+
import com.google.protobuf.ByteString
20+
import omega_edit.ChangeKind.CHANGE_INSERT
21+
import omega_edit._
22+
import org.apache.pekko.stream.Materializer
23+
import org.apache.pekko.stream.scaladsl.Sink
24+
import org.scalatest.OptionValues
25+
import org.scalatest.matchers.should.Matchers
26+
import org.scalatest.wordspec.AsyncWordSpecLike
27+
28+
import scala.concurrent.duration.DurationInt
29+
30+
class Bug976Spec extends AsyncWordSpecLike with Matchers with OptionValues with EditorServiceSupport {
31+
"GitHub issue 976" should useService { implicit svc =>
32+
val requested_vid = "my_viewport"
33+
implicit val mat = Materializer.matFromSystem(svc.system)
34+
35+
"validate vid on viewport events and unsubs" in {
36+
for {
37+
s <- svc.createSession(CreateSessionRequest.defaultInstance)
38+
sid = s.sessionId
39+
v <- svc.createViewport(CreateViewportRequest(s.sessionId, 1, 0, false, Some(requested_vid)))
40+
vid = v.viewportId
41+
sub = svc.subscribeToViewportEvents(EventSubscriptionRequest(vid, None))
42+
_ = svc.submitChange(ChangeRequest(sid, CHANGE_INSERT, 0, 1, Some(ByteString.fromHex("ff"))))
43+
evt <- sub.completionTimeout(1.second).runWith(Sink.headOption)
44+
unsub <- svc.unsubscribeToViewportEvents(ObjectId(vid))
45+
} yield {
46+
vid should startWith(sid)
47+
evt.value should matchPattern { case ViewportEvent(`sid`, `vid`, _, _, _, _, _, _) => }
48+
unsub.id shouldBe vid
49+
val Array(s, v) = vid.split(":")
50+
s shouldBe sid
51+
v shouldBe requested_vid
52+
}
53+
}
54+
}
55+
}

0 commit comments

Comments
 (0)