Skip to content

JPDA: Fix Save Value to File for large debugger strings - #9635

Open
matthiasblaesing wants to merge 4 commits into
apache:masterfrom
matthiasblaesing:full_string
Open

matthiasblaesing wants to merge 4 commits into
apache:masterfrom
matthiasblaesing:full_string

Conversation

@matthiasblaesing

Copy link
Copy Markdown
Contributor

While debugging with JPDA NetBeans makes strings directly available as long as they are shorter than 100000 characters. Longer strings are truncated to that length.

The full string can be made available on demand and saved to a file.

Before this change the truncated string was used as a key in a WeakHashmap that held the data required to fetch the full string. This causes problems:

  • it was observed that the contents of the WeakHashMap was collected to early, rendering the full string inaccessible

  • the truncated string might not be unique, which might result in crosstalk between strings displays

This change switch from providing truncated strings to consumers to providing as StringInfo object that not only holds the shorted string, but also all the info that is required to fetch the full string.

The caching that was done based on the shorted string was switched to StringReferences as keys, which are documented to be equal when the backing VM and string are identical.

While debugging with JPDA NetBeans makes strings directly available
as long as they are shorter than 100000 characters. Longer strings
are truncated to that length.

The full string can be made available on demand and saved to a file.

Before this change the truncated string was used as a key in a
WeakHashmap that held the data required to fetch the full string. This
causes problems:

- it was observed that the contents of the WeakHashMap was collected
  to early, rendering the full string inaccessible

- the truncated string might not be unique, which might result in
  crosstalk between strings displays

This change switch from providing truncated strings to consumers to
providing as StringInfo object that not only holds the shorted string,
but also all the info that is required to fetch the full string.

The caching that was done based on the shorted string was switched to
StringReferences as keys, which are documented to be equal when the
backing VM and string are identical.
@matthiasblaesing matthiasblaesing added Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form) ci:dev-build [ci] produce a dev-build zip artifact (7 days expiration, see link on workflow summary page) debugger labels Sep 22, 2026
return this.shortendString;
}

public int getShortendLength() {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a typo. It is "shortened" according to my dictionary.

meth public int getShortendLength()
meth public java.io.Reader getContent()
meth public java.lang.String getFullString()
meth public java.lang.String getShortendString()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • must these methods appear in API changes list?
    • I guess they have to as they are accessed from a different package
    • and this package is listed as API
  • can only one of the methods be added?
    • seems to me getShortenedString().length() would be sufficient

String str = ShortenedStrings.getStringWithLengthControl(sr);
Object stringData = ShortenedStrings.getStringWithLengthControl(sr);
String str;
if(stringData instanceof ShortenedStrings.StringInfo si) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing space after if

string = shortedString;
StringInfo si = new StringInfo(sr, shortedString, stringLength, sa, backingEncoding, isLittleEndian);
synchronized (stringCache) {
stringCache.put(sr, si);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • multiple threads can be running this stringCache.put(sr, si) code
  • for the same sr, but different si
    • that was also the old behavior, it is not changed by this PR
  • I guess that doesn't matter
    • at most the StringInfo is constructed multiple times in parallel

- Remove "..." suffix from shortened string in StringInfo (it is a
  display artifact). This removes requirement for arbitrary length
  corrections (the three dots are not part of the real data)
- fix wrong spelling shortend -> shortened
- Remove unnessary method getShortendLength
- fix missing space in if-block
- simplify BigStringCustomEditor and remove dead code
@matthiasblaesing

Copy link
Copy Markdown
Contributor Author

@jtulach thanks for the review.

  • For the typo: you are tight the correct spelling is "shortened". This does not match my language feeling, but then I'm wrong ... 🤷
  • The missing space is added
  • The signature file exists because debugger.jpda has a friend-only API. That will cause the sigfile to be generated. The friends are in the code base and and the only usage outside the module is in debugger.jpda.ui. I don't see value in keeping this API compatible based on this. I reduced the method count though as suggested.
  • For the threading question: no for the same StringReference only a single StringInfo will be created. If getStringWithLengthControl is called twice in parallel for the same StringReference the guard at the start of getStringWithLengthControl will be hit https://github.com/matthiasblaesing/netbeans/blob/9a7b4ac83ebf23c0da76c1cc982781b9aa5280d1/java/debugger.jpda/src/org/netbeans/modules/debugger/jpda/models/ShortenedStrings.java#L165-L184. Thread 1 will add the string reference to the retrievingStrings set, Thread 2 will find the StringReference there and stop execution and #wait() for it.

@jtulach

jtulach commented Sep 24, 2026

Copy link
Copy Markdown
Contributor

I don't see value in keeping this API compatible based on this.

  • Your change is compatible. Adding a method into (effectively) final class is fully compatible binary change.
    • I suggest to increase spec version of the module and annotate the method with @since
  • I'd also suggest to make a note in apichanges.xml, if it existed (they don't so skip this)
  • overall I'd advocate to be compatible even for friend APIs
  • debugger.jpda has a friend-only API ... the only usage outside the module is in debugger.jpda.ui.
  • my long term plan has been to incrementally turn friend-only APIs into public ones
  • but I guess it is not the right moment now as the usage is inner module anyway

@jtulach

jtulach commented Sep 24, 2026

Copy link
Copy Markdown
Contributor
  • For the threading question: no for the same StringReference only a single StringInfo will be created. If getStringWithLengthControl is called twice in parallel for the same StringReference the guard at the start of getStringWithLengthControl will be hit
  • I see. The Synchronization pattern is (almost) right, but done with old good Java synchronization primitives
    • the case of catch (InterruptedException ex) {} seems to be handle improperly
  • these days one would code this with Futures
    • the first request adds a pending Future to the concurrent map
    • and starts computing it then
    • the other requests just obtain the Future and call get()
  • Enso did it for example here

- don't swallow the InterruptedException in
  ShortenedStrings#getStringWithLengthControl move handling to callers
  that already handle the other potential fetch failures
@matthiasblaesing

Copy link
Copy Markdown
Contributor Author

For the question compatible change or not: This change is not compatible. I removed StringInfo#getShortLength, that could be readded, but I also removed ShortenedStrings.getShortenedInfo(String) that makes no sense under the new approach and can not be emulated.

For the retrieval logic/sychronization pattern: I agree that a different approach might be better, but a quick rewrite did not yield more readable code so I opted to go minimal. The InterruptedException id now not swallowed anymore but passed to the caller. The callers already have to deal with retrieval problems and thus are the correct place to handle this. (Pushed as update)

@matthiasblaesing

Copy link
Copy Markdown
Contributor Author

@entlicher would you mind having a look at this? According to the class javadoc you were the original author of ShortenedStrings.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:dev-build [ci] produce a dev-build zip artifact (7 days expiration, see link on workflow summary page) debugger Java [ci] enable extra Java tests (java.completion, java.source.base, java.hints, refactoring.java, form)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants