noties / Markwon

Android markdown library (no WebView)
https://noties.io/Markwon/
Apache License 2.0
2.76k stars 313 forks source link

MarkwonInlineParserPlugin is not thread-safe #432

Open CRaFT4ik opened 1 year ago

CRaFT4ik commented 1 year ago
  1. Please specify expected/actual behavior

I expect that parallel calls toMarkdown on the same Markwon instance with MarkwonInlineParserPlugin will produce a deterministic result. In fact, I get errors related to incorrect parsing (my links in [text](link) format are not parsing correctly).

  1. Please specify conditions/steps to reproduce (layout, code, markdown used, etc)

Although I'm using different code in my android app, I've sketched an example that reproduces the problem:

        val input = "[Link text Here](https://link-url-here.org)"

        val markwon = Markwon.builder(application)
            .usePlugin(MarkwonInlineParserPlugin.create(MarkwonInlineParser.factoryBuilderNoDefaults()).apply {
                factoryBuilder()
                    .referencesEnabled(true)
                    .addInlineProcessor(OpenBracketInlineProcessor())
                    .addInlineProcessor(CloseBracketInlineProcessor())
            }).build()

        with(CoroutineScope(Dispatchers.IO)) {
            repeat(5) {
                launch {
                    Timber.tag("MarkwonInstance").w("$it: ${markwon.toMarkdown(input).toHtml()}")
                }
            }
        }

output:

 4: <p dir="ltr">[Link text Here](https://link-url-here.org)</p>
 0: <p dir="ltr">[Link text Here]</p>
 3: <p dir="ltr">[Link text Here]</p>
 1: <p dir="ltr">]</p>
 2: <p dir="ltr">[Link text Here<a href="https://link-url-here.org">Link text Here</a></p>

I suspect this doesn't work correctly for all *Processors with MarkwonInlineParser.

noties commented 1 year ago

Hello @CRaFT4ik ,

well, true, markwon is not thread safe. It was not intended to be shared between multiple threads, as there is some internal state involved during parsing in some plugins. Normally, you would need create an instance of Markwon when you need it without sharing it