drewr/postal

memory leak

daonsh opened this issue · 3 comments

I'm pretty sure there's a memory leak in send-message, at least under some circumstances. I have narrowed it down to just running this loop:

(dotimes [n 2000]
(let [uuid1 (myutil/random-uuid)
uuid2 (myutil/random-uuid)
uuid3 (myutil/random-uuid)]
(println "sending TEST email subj " uuid1)
(try (let [fromname "my name blalablabla" ]
(postalemail/send-message mycred/conn-from-me
{
:from (str fromname " <" mycred/crm-email ">")
:to blabla@gmail.com
:subject uuid2
:body [ {:type "text/html" :content uuid3 }] })
true) ; success in sending e-mail
(catch Exception e (do (myutil/write-important-log
; failed sending e-mail
"TEST Send-Email exception: " (.getMessage e))
false ; failure in sending
)))
(Thread/sleep 10000)
))

When this loop is running, and a message is sent every 10sec, I can clearly see memory use rising (using top) from 8.3% to 9.1% over around 15 minutes. (sending around 50-100 e-mails).
If this code does not run, memory use doesn't rise.

If anyone has any idea on why that happens, and how to fix that, I'd be happy to hear.
I suppose some memory needs to be freed after sending the e-mail, but I don't really know the Java API and how to do that.

Thanks

top is really the wrong way to measure Java memory usage -- generally speaking, once the JVM finds that an object is unreferenced and can be freed, it returns that memory to its own heap, not to the OS -- and this check can occur an indeterminate amount of time later, depending on numerous tunable parameters.

The right tool to use is a Java memory profiler such as YourKit -- that way we can determine whether this is memory that's still attached to an unreferenced object that hasn't yet been freed; attached to a referenced object (and determine where those references are coming from); or actually already freed to the JVM heap but not to the OS as a whole.

I do own a YourKit license, and use postal commercially, so I can justify doing this on company time; I'll follow up here when I have a result.

@shaulid, actually, taking a closer look at the code above -- could you perhaps try to provide a full standalone reproducer?

There's a lot that's hidden under myutil or mycred -- I can't tell if the bug only happens with STARTTLS enabled, for example, and I'd rather not fumble around on my own. (Similarly, I'll be spinning up a stub mail server than just consumes messages and does nothing with them; how much configuration complexity that stub needs to have is something I'd prefer to actually have a canonical answer to, rather than need to figure out on my own).

A reproducer coupled with instructions on how to start a stub server that reproducer has been tested to work with (for instance, python -m smtpd -n -c DebuggingServer localhost:1025 to start a trivial server on port 1025) would be ideal.

Hi Charles,

Thanks for looking into this issue.
I'm not a Clojure expert and it's too difficult for me to quickly create the right project.clj for this.

However I did provide a very small codeblock which actually blocks (there're no other threads that can cause the memory leak) and I ran it for a while using my (not-so-reliable) test.

I'd be happy to provide the missing stuff -
The messages/subjects are just uuid's - nothing practical, just to make sure Gmail doesn't block duplicate messages.
(defn random-uuid [] (str (java.util.UUID/randomUUID)))

crm-email is just a string of email address
(def crm-email "email-removed")

The connection is defined
(def conn-from-me {:host "smtp.gmail.com"
;:host "smtp-relay.gmail.com"
:ssl true
:user crm-email
:pass "password-removed"})

Please let me know if that helps.
After googling STARTTLS I don't if it's relevant and I don't know how to set/unset it here.

Thanks