drewr / postal

Clojure email support
MIT License
586 stars 85 forks source link

memory leak #86

Open daonsh opened 7 years ago

daonsh commented 7 years ago

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

charles-dyfis-net commented 7 years ago

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.

charles-dyfis-net commented 7 years ago

@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.

daonsh commented 7 years ago

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