Showing posts with label java. Show all posts
Showing posts with label java. Show all posts

Wednesday, March 22, 2023

Java 20

I don't program much in Java these days, just minimal maintenance on jSuneido. But I still have an interest in what's happening with the language.

Once upon a time, Java had a problem - no new releases for a long time. They solved that by switching to a fixed release every six months. For a while we got all kinds of new features. It was great. But lately it's been a new problem, the releases have been on time, but they haven't "released" much. Here's the complete list of features for the latest release:

Features

429: Scoped Values (Incubator)
432: Record Patterns (Second Preview)
433: Pattern Matching for switch (Fourth Preview)
434: Foreign Function & Memory API (Second Preview)
436: Virtual Threads (Second Preview)
437: Structured Concurrency (Second Incubator)
438: Vector API (Fifth Incubator)

Every feature is either incubator or preview. I understand the need for incubator and preview. But "Fourth Preview" and "Fifth Incubator"? Yes, you want to get it "right", but if you wait for perfect in the eyes of committees, you could be waiting a long time.

I wonder if this is partly a matter of Java's "enterprise" association. Let's face it, the hip kids aren't using Java. They don't care if e.g. Java Virtual Threads are ever released. On the other hand the enterprise customers don't really want new features. They're still running Java 8. But they do want to be reassured that Java is alive and well. Previews and incubators serve that purpose just fine.

Maybe I'm just impatient. I'm excited about some of the new features (and others, like Valhalla that have been "coming soon" for many years but haven't even made it to incubation). But I don't have that many decades left.

Sunday, December 04, 2022

Blogging, Suneido, and Programming Languages

Another blogger I follow has announced they are stopping blogging. It's sad (to me) how the world has shifted away from blogging. Of course, there are still active blogs, but far fewer than there were. I thought maybe the pandemic would give people time to do more blogging but it didn't seem to work that way. Of course, it always surprised me that smart productive busy people would take the time to write good blog posts.

I'm no exception. I post less and less often, other than photographs on my non-work blog. But even that is a shift from my past longer text posts. Readers have dwindled too. (Not that I ever had a big audience.)

But looking back through some of my blog posts, I'm glad I wrote them even if hardly anyone read them. It's a bit like a sporadic diary, but more thoughtfully written than a personal diary would be. I started blogging in 2005. It would have been interesting to go back farther but even in 2005 the tech world was a different place than today.

Thinking about this led me back here. I was writing a document for my staff, and some of it seemed of slightly more general interest so I thought I'd write about it.

Here's roughly when the versions of Suneido went into "production" (used by customers)

  • 2000 cSuneido (C++)
  • 2010 jSuneido (Java)
  • 2020 gSuneido client (Go)
  • 2022 gSuneido server
  • 2022 suneido.js (browser client)

Two data points doesn't mean much, but it's interesting that it was roughly 10 years between the major versions. The concurrent release of suneido.js is because another programmer did the development. (I started it, but developing both gSuneido and suneido.js at the same time was too much.)

All three implementation languages (C++, Java, Go) were in their early stages when I started using them. I was lucky that all went on to become mainstream. Some of the other languages I considered, such as C#/.Net and Kotlin have also been successful. Scala and D not as much, although they're still around.

C++ is a fascinating language. But it's too complex and it's unsafe. Life is too short. (gSuneido does have some unsafe code to interface with Windows and it has been the source of the hardest bugs.)

Java has a world class runtime with a great garbage collector and JIT compiler. But the language itself is not really a good fit for low level systems programming. gSuneido, with a simple byte code interpreter and no JIT still manages to outperform jSuneido. If Project Valhalla ever gets released that will help. But it's been "coming soon" since 2014.

Go felt like a bit of a gamble at the time. The garbage collector wasn't very good and neither was performance. But the language was a good fit for implementing Suneido. And it seemed like Google was committed to it. Although I wish it was a little less of a one-company language.

One of the things that bugged me about Go was its lack of generics. But now that it has them, I don't really use them that much. The people that resisted generics would say "I told you so". But it did simplify certain kinds of code and I'm still glad to have it when I need it. 

I wish Go had better support for immutability. C++ and Java aren't much better but at least they have a little bit. Immutability (and pure functions) are one of the best tools to handle concurrency, and concurrency is supposed to be Go's strong point. I think there's potential for a conventional mainstream language with strong immutability. I realize there are functional languages with strong immutability, but so far they're not really mainstream.

The big thing these days is Rust, but Suneido is garbage collected, and unless you want to write your own garbage collector (no thanks, been there, done that) the implementation language also needs to be garbage collected.

As far as the Suneido language itself, I think it has stood the test of time reasonably well. It was never intended to be especially novel or original, although at the time object-oriented programming and dynamic languages were leading edge. There have been a few minor adjustments to the language, a bit of syntactic sugar here and there, but for the most part code written 20 years ago would still run today. There are a few things I'd change but overall, when I program in Suneido, there's not a lot I wish was different.

Wednesday, May 06, 2020

Eclipse Bug

This one had me puzzled for a while. I went to run some jSuneido code in Eclipse and I got a weird verifyError i.e. something wrong with the byte code. Probably just a glitch, a corrupted file or something. I recompiled the code. Same error. Meanwhile, all the jUnit tests (running all the time through Infinitest) were passing fine. And when I built the final jar, it worked fine as well.

Maybe my Eclipse or my configuration was messed up. I connected to my office machine and tried it there. And got the same error.

The problem was, I couldn't remember when this last worked. I've been mostly working in Go on gSuneido. I've made the occasional minor changes in jSuneido but probably didn't try to run anything inside Eclipse.

Maybe it was a Java 14 issue? Eclipse isn't very good at supporting the latest version of Java. Currently you have to install a patch to get Java 14 support. So I installed Java 13 and tried again with that. Same error.

I tried reinstalling Eclipse (2020-03). Didn't help. I tried installing the previous version of Eclipse (2019-12). Still no good.

I have to admit I was getting quite frustrated by this point. I'd had several computer issues in the last few days and I didn't need yet another one. I even considered switching IDE's. I installed JetBrains intelliJ (I have a license for all their products) and it had no problem running the code.  But I struggled just to get that far with intelliJ. It's quite an investment to learn an IDE and I didn't really want to start over. Especially when I'm not doing much Java development these days.

I narrowed it down to a problem with the Eclipse Java compiler (they have their own, separate from the JDK). If I compiled with the JDK it was fine, but if I compiled with Eclipse, it wouldn't work inside Eclipse or outside.

I vaguely remembered that I had fallen a bit behind with Eclipse and had jumped over the previous version (2019-12). So I installed 2019-09. And it worked! Finally!

By this point I was fairly sure it was an Eclipse Java compiler bug. I searched the Eclipse bug database (and the web) but couldn't find anything related.

I figured I should report a bug, but was, as usual, hesitant. There are lots of Eclipse users, why was I the only one to run into this? Was it something I was doing wrong? (Although in that case, why did the JDK work fine?) It sounds silly, but I was afraid of looking stupid in public.

I decided if I could create a simple example that reproduced the issue, then I'd file a bug. Luckily, by then I had a good idea of the issue and it wasn't hard to make the example.

Basically the bug came from the equivalent of:

MyFunc(condition ? derived1() : derived2())

derived1 and derived2 returned private classes with a common public parent (all from another package).

The type of the ?: expression should be the common public parent. But in the Eclipse bytecode it was Object (basically an unknown type). For some reason it wasn't looking past the private classes to see the common public parent. Curiously, if I assigned the ?: expression to a variable of the parent type, it handled that fine. Or if I rewrote the ?: as an if-else it was also fine. Using javap to look at the bytecode I could see the JDK stack map showed the correct parent type, but the Eclipse stack map showed Object.

I posted the bug and the next morning I saw:

I will investigate. Reproduced with your example.
Thanks! Great test case. This is really helpful

It was good to see it was valid. Later comments confirmed the bug and that the fix was trivial and would go into future versions.

Tuesday, December 19, 2017

Concurrency Again

It started with long update transactions. Mostly we ignored them - some of our customers have slow servers, and sometimes they try to do transactions that are too large.

jSuneido aborts long update transactions as a defense mechanism. Its optimistic concurrency control has to track overlapping transactions, so if a transaction takes a long time, it can lead to a lot of overlaps.

But some of these long update transactions didn't make much sense. They appeared to be slow in the final commit stage. This stage just writes to the memory mapped files and normally is very fast, nowhere near the 20 second transaction limit.

I started going through the code, trying to see what might explain this. I wondered if it might have something to do with the background (once a minute) task that calls force on the MappedByteBuffers to flush them to external storage. Trying to ensure that files are durably written is tricky these days with so many levels of caching etc. but it seems worth trying.

I would have thought that force itself would be fast since most of the work would be done at the operating system level. But the documentation says that it doesn't return till it finishes writing to storage. I added some logging and found that force is usually fast (a millisecond) but occasionally slow (10's of seconds). There didn't seem to be a particular reason for the slow ones. I assume Windows is doing something that blocks.

The slow force would be enough to cause long update transactions, but force was supposed to be happening in a background thread so it shouldn't have blocked anything.

Crap! The force method was synchronized. And so was a method that transactions needed to use to commit. So the force would block the commit and could cause the problem. The problem was relatively rare because the force was only once a minute and usually fast, and it would have to hit exactly when a transaction was committing.

Did force really need to be synchronized? Did the other methods? Back to the age old problems of concurrency with shared memory and locking.

I removed the synchronized. But then I worried about accessing the array of MappedByteBuffers without any locking. I could make the array reference volatile but not the array elements. So I changed it to use AtomicReferenceArray.

At first I thought it was working ok. I ran tests with a bunch of threads hammering on the database and it seemed to work ok. But then my database got corrupted. Was my change failing? I ran the tests again and everything was fine. Hmmm. I changed the force to run every 10 seconds instead of every minute. Sure enough, now the database would get corrupted every time I ran the test.

It still didn't make sense to me that removing synchronized from force would corrupt the data. I noticed that my force method was also writing a few zeros to the end of the file to force the last modified date of the file to get updated. I could see that having concurrency issues. I changed it to rewrite the first few bytes of the file header instead. That fixed it, no more corruption. Hopefully it's smart enough to see that nothing has actually changed and not physically write to storage.

Everything looked good according to my tests, but that's generally not sufficient when it comes to concurrency. I also feel like I should convince myself that it is "safe", even if only informally.

Writing to the database (the final stage of an update transaction) was protected by a lock. So there could only be a single writer at a time. (Yes, that's a bottleneck, but it's a small part of the overall process so it doesn't reduce concurrency too much.)

But there are multiple reader threads. So the question is visibility, not exclusion. The Java Memory Model makes no guarantees of visibility without either volatile or locking. To ensure visibility I needed writers to release a lock when they were finished, and readers to acquire that same lock when they were starting.

For database transactions, that seemed to be covered. Transactions called synchronized methods (on the same object) at the beginning and end.

But there were other readers besides database transactions. The database checking process was one. It also turned out to be ok because it acquired the commit lock briefly at the start. (And writers released it at the end.)

That left the force process. Part of the problem was that it's low level code. It doesn't (and shouldn't) "know" about transactions or commit locks etc. I needed something at the same level. Luckily, writers called a particular low level method when they ended so I could synchronize that method along with the force method. Oops! Just about repeated the original mistake of force holding a lock while it was working. It didn't need to hold it since the goal wasn't exclusion. I just needed to acquire (and then release) the lock (to ensure visibility). So I made a dummy "sync" method that was synchronized and called that at the start of the force method.

I think I've convinced myself that it is "safe". I wouldn't say I'm one hundred percent sure, but reasonably confident.

Another question is fragility. Would future changes be likely to break this? Minor changes seem pretty safe. The things I'm depending on are reasonably fundamental. If I made bigger changes it could be dangerous, but presumably if I'm making bigger changes I would reconsider concurrency. And I did document it.

So if visibility and exclusion are covered, do I need to use an AtomicReferenceArray? Presumably not, so I reverted those changes and went back to a plain old array.

I did another round of testing on both OS X and Windows and didn't encounter any problems.

The problem with this kind of concurrency issue is that you can't just throw locks at it. The problems are just as likely to come from too many locks as from not enough. I wish you could just lock everything and be done with it. But if you do that, you'll probably have deadlocks, and you'll be back to single threaded performance.

Sunday, January 01, 2017

Java ByteBuffer Gotcha

I had a weird bug in some Java code in jSuneido that took me a while to figure out. I briefly thought I'd found a bug in the ByteBuffer implementation, although I realized that was a low probability. (Especially given the confusing nature of ByteBuffer.) In the end it makes sense, although it perhaps could be documented better.

Here's the scenario - you slice or duplicate a ByteBuffer. This makes a new ByteBuffer instance that shares its data with the original. Then you compact the original buffer. Note - this does not modify the data that you are interested in. However, it does move it.

ByteBuffer buf = ByteBuffer.allocate(8192);
for (int i = 0; i <= 300; ++i)
    buf.put((byte) i);
buf.flip();
for (int i = 0; i < 12; ++i)
    buf.get();
ByteBuffer slice = buf.duplicate();
System.out.println(slice.get(0));
buf.compact();
System.out.println(slice.get(0));

This will print 0 and then 12, i.e. the slice has changed, even though you didn't explicitly modify the buffer.

In retrospect it's obvious - compact does alter the buffer, which is shared with the slice. So the contents of the slice "changes".

I'd be tempted to add a warning to the documentation for compact that it will "invalidate" any existing slices or duplicates, the same way that C++ documentation points out which operations invalidate iterators. But maybe it should be obvious.

I'm mostly posting this because my searches for the problem didn't find anything. Perhaps this will help someone in the future find the problem quicker.

Tuesday, September 22, 2015

A Bug Hunter's Life

I recently spent two weeks tracking down a single bug. Needless to say, it was a frustrating process. Like most such things, what made it difficult was not a single problem, it was a whole cascade of errors, including many during the debugging process. I like to think I'm fairly organized and methodical, especially when working on a tough bug. I take notes on what I'm doing, what my assumptions are, what I've tried, what the results were. And yet I still managed to make multiple major mistakes and go a long way down the wrong paths based on false assumptions I should have questioned much sooner.

It started with a customer getting a corrupted database. Most of the time this is from hardware issues or crashing. But this one appeared to be caused by a bug in the code.

It wasn't easy to look into because all I had was the corrupt database, with no clues as to how it might have gotten that way. Thankfully, jSuneido's append-only database means that the database itself forms a kind of log. Based on that I could see that two transactions had committed conflicting updates - something that the whole transaction system is designed to prevent.

It seemed a pretty safe bet that this was a concurrency related issue - my least favorite kind of bug :-( I wasn't surprised that I couldn't make it happen. With roughly 1000 systems running 24x7 this was the only case I was aware of. So my sole sources of information were that database and the code itself.

In order to detect conflicts at commit time (jSuneido uses optimistic concurrency control) you need to know the outstanding overlapping update transactions (i.e. ones that had started after this one and had not completed yet). Perhaps the bug was in determining this set of transactions? I wrote a completely new implementation of this code and used it to cross-check.

We deployed this version to our customers and pretty quickly started to get lots of errors. But only a couple were from the new cross-check. The rest were from code I hadn't touched. And yet they quite clearly started when the new version was deployed. Strange.

The errors indicated that sometimes when it went to complete a transaction it would be missing from the list of outstanding ones. Again, this shouldn't be possible. That would explain why the overlapping set would be wrong, so it made a certain amount of sense. I spent days studying the code and trying (unsuccessfully) to figure out how it was happening.

This turned out to be completely off track - it was a just problem with my new cross checking code.

The stack traces showed a transaction commit that turned into an abort. That happens if an update transaction doesn't end up doing any updates. So I wrote a bunch of tests with those kinds of transactions in the mix, but they all worked fine. If nothing else, the code was getting reviewed and tested pretty heavily.

Finally, after wasting days on this, I realized there was another way for a commit to turn into an abort. The relevant part of the code looked like:

try {
     // commit stuff
} finally {
     abortIfNotComplete();
}

What was happening was that an exception during the commit stuff would get overridden (and lost) if there was an error in abortIfNotComplete(). So I wasn't seeing the original error at all. And the reason that the transaction was missing from the list was that it had got far enough into the commit to remove it.

I'm not sure why I wrote it using finally. In hindsight it was obviously a bad idea. I rewrote it like:

try {
     // commit stuff
} catch (Throwable e) {
     try {
          abortIfNotComplete();
     } catch (Throwable e2) {
          e.addSuppressed(e2);
          throw e;
     }

addSuppressed was added in Java 7. It is used, for example, in try-with-resources for exceptions when closing after another exception.

Part of the reason I didn't realize which path was being taken in the code was that I didn't have line numbers in the stack traces.  That was because in the Ant build task "debuglevel=vars" had been added. The intent had been to increase the amount of debugging information. But because of the way Ant and the Java -g option work, this had actually meant only variable information. I changed it to "debug=true" which becomes "-g" which means all debug information (file, line, and vars). Once this change percolated through and I started to get stack traces with line numbers, then I could see which path was being taken in the code.

One big step forward was when I was finally able to recreate the bug. Running random updates (of certain kinds) in a bunch of threads would encounter the bug within a few seconds. It wasn't the ideal recreation because the bug would occur buried within a lot of other activity, so it still wasn't possible to determine exactly what was happening.

I couldn't be 100% sure that what I was able to recreate was the same bug, but the end result was more or less identical - two transactions deleting the same record. But confusingly, the errors that I saw were about duplicate records being output, leading me to spend a bunch of time looking at the output code.

A huge mistake that I made repeatedly, without realizing it, was that I was building one variation of the jSuneido jar, and then testing with a different out of date one. So I wasn't actually testing with the changes I was making. This was because, for faster turnaround, I wasn't running a full build, I was just building the main jar that we deploy. But I was testing using the jar with Win32 support so I could use the IDE. This explained the totally baffling behavior where the debugging output that I was 100% certain should be triggered didn't appear at all. Which confused me and made me think I was totally wrong in my ideas about what was happening.

This mistake cost me about 4 days. I actually had the bug fixed, but it kept happening because I was testing with the old jar. Of course, since the bug still happened I assumed that I had not located it yet and I went looking for it in other places where, of course, I didn't find it.

Even worse, I thought my method of recreating the bug was quite "fragile". For instance, it would not happen running in the Eclipse debugger. Coming from C and C++, I didn't find that too surprising. And although Java should be more predicable, there would still be timing and threading differences which could affect concurrency. But I was totally wrong - the reason I couldn't recreate it within Eclipse was that I was running the latest code, which had the fix. Arghhh!

The actual bug and fix are obvious in hindsight. Throughout the long process I suspected that would be the likely result. But that didn't make it any easier, in fact it made it even more frustrating.

jSuneido's database uses optimistic concurrency. That means concurrent transactions proceed independently without any locking. Then when it comes time to commit, they verify that nothing has happened that would conflict. For example, they might find that another transaction has output a duplicate key. In which case the transaction will abort due to the conflict. Or it might find that another transaction has deleted a key that we also deleted. And that's where the bug was. I had made the assumption (back when I wrote this code) that it was ok if two concurrent transactions deleted the same key. After all, the end result is the same. And it's still serializable (i.e. the result is as if the transactions had happened one after another instead of concurrently).

The problem is not the deletes by themselves. If that was all the transactions were doing, my logic was correct. But transactions aren't that simple. They perform other actions based on whether the delete succeeded. For example, deleting an old version of a record and outputting a new version.  If you allow two transactions to think they deleted the old version, then they will, incorrectly, both output a new version. (leading to the duplicate outputs I was seeing)

All I had to do to fix the bug was to make duplicate deletes a conflict, like I already did with duplicate outputs. Two weeks for one line of code. How's that for productivity.

One interesting part of this whole adventure was that although it was a concurrency bug, it wasn't the kind of subtle interaction problem that I really fear. It was, in hindsight, a fairly clear, obvious issue.




Wednesday, July 15, 2015

Problem with Eclipse Mars on Windows after Java Update

I installed the new Java update on my Windows work machine, and removed the old version.

When I tried to run Eclipse (Mars) it said it couldn't find a JRE at the path of the old version of Java.

That made sense because I'd removed it. But why was it specifically looking for that path?

When I'd updated Java in the past Eclipse had found the new version fine.

I found that in the eclipse.ini file it had a -vm option set to the old version of Java. I removed this and now Eclipse starts fine.

I wonder if this was a result of installing the recently released Eclipse Mars with the Oomph installer?

Note: The Oomph installer puts Eclipse in your user folder (e.g. c:/Users/andrew in my case) rather than in Program Files. This may be a workaround to not require admin privileges to install.

Thursday, April 23, 2015

When is a PriorityQueue Not Ordered?

aka When does a final field change?

Background

jSuneido limits how long a database update transaction can be active. This is because active update transactions consume resources.

Read-only transactions consume little resources and are not limited. (This is a benefit of the append-only database structure.)

There are two parts to the limiting. If an update transaction commits and it was active more than a certain amount of time (currently 5 seconds) then a warning is logged but otherwise the transaction completes normally.

A separate process runs periodically (currently once per second) and any update transactions that have been active for too long (default 10 seconds) are aborted. A PriorityQueue is used for this so that only the longest running update transaction needs to be looked at.

Problem

Recently I noticed that some of our customers were getting the warning, but the duration given was well over the abort limit. That should have been impossible - the transactions should have been aborted before they reached that point. My first thought was that I had "broken" it with some recent change. But going back through the logs it looked like this had been happening for quite a while.

Searching the logs, I found that there were some transactions getting aborted due to duration, so that part of the code was working at least some of the time.

I added some debugging and found that the problem was that PriorityQueue peek was not returning the oldest transaction. That seemed impossible since by definition PriorityQueue peek returns the smallest element.

I checked my comparator but it was simple and seemed correct. I wrote some equivalent test code and it worked fine.

I started searching on the internet to see if anyone else had run into similar problems. Sure enough they had, but the reason was that they had been modifying the elements after inserting them. (Similar to problems if you modify elements after inserting into hash tables.)

But the field I was ordering by was final so it couldn't be modified.

Or could it? A final field still has to get set at some point. Sure enough, I was inserting the transactions into the queue in the super constructor, which ran before the field was initialized. So the queue insertion was always seeing a value of zero and the order was undefined. Argh!

(Java normally prevents this kind of problem, but I was casting to a derived class in code called by a base class constructor. Moral of the story - avoid tricky code!)

In case you're wondering, I had tested. But it worked when testing since I would only have a single active transaction, and the order was irrelevant.

It was an easy fix to reorganize the code slightly to ensure the queue insertion was done after the field was initialized. (Although that splits what used to be a single synchronized method into two, which makes me nervous about concurrency problems. I think it's ok, hopefully it won't be the subject of a future blog post!)

Tuesday, May 27, 2014

Java 8 Performance

I was just looking at some stats on the average time our customers' servers take to run our application test suite.

I noticed on a particular day the times dropped from an average of 240 seconds to an average of 200 seconds. (These are averages from about 240 customer sites.) The numbers are generally quite stable so I was curious what changed.

I discovered that was the day we updated everyone to the Java 8 JRE so it looks like that's the reason for the improvement. Assuming that's the correct explanation that's a pretty nice upgrade!

It made sense that it was Java related since customers running the older cSuneido did not show any improvement that day.

Note: jSuneido is still compiled for Java 7, this improvement would just be from the runtime.

Thursday, April 17, 2014

Overlooking the Simple Solution

For no particular reason I've been thinking about concatenating strings. (I know, get a life, but this is at least part of my life.) It was partly prompted by thinking about Go and how it might work to implement different facets of Suneido. 

Most languages warn you about the poor performance of building a large string by repeated concatenation. They recommend using a StringBuilder (Java) or its equivalent. 

But Suneido explicitly optimizes repeated concatenation internally so the programmer doesn't have to worry about when to concatenate and when to switch to "building". 

In cSuneido (the original C++ implementation) concatenation just creates a linked list of substrings, deferring the actual allocation and copying. 

Originally, I ported that approach to jSuneido (the Java version). But I cut a few corners that I thought were safe to cut. That came back to haunt me. Rather than fix the problems I looked for a better solution. (There are some issues when the linked list gets too big.) I considered some kind of merge tree but decided that was more complex than necessary.

Instead of a linked list I used an array of pieces which was expanded as needed. If the array got big it would merge small pieces. That has been working fine. 

Analyzing the problem from a more theoretical basis I figured the worst approach is repeated naive concatenation and the best (?) is something like StringBuilder that uses a buffer that expands e.g. by doubling in size. My array approach is somewhere in between, as is a merge tree. 

At that point it struck me that I could just use a StringBuilder rather than my array approach. Duh!

That eliminated about a hundred lines of code and ran about 20% faster. 

I feel stupid for not thinking of this sooner. It seems so obvious (now!) But I was stuck on the idea of deferring concatenating. 

And now I feel even more stupid because I just searched my own blog and found that I did consider a buffer approach but decided it required too much copying. (String Building Internals) Using a StringBuilder does mean copying into it and then eventually copying the result back out. But considering that Java compiles string concatenation into using StringBuilder, the overhead can't be too big. (Go, on the other hand, compiles string concatenation into calls to a runtime function that allocates a new string and copies directly into it, without any intermediate buffer.)
One advantage of my original linked list approach is that everything is immutable and therefore threadsafe without locking. That's attractive. Both the array and the StringBuilder are mutable and require locking. That's not a big deal on Java since the locks will almost always be uncontested and therefore very fast. And the locking is at the leaves of the call tree so they should be safe from issues like deadlock. 

But in Go, locking is less acceptable and an immutable solution would be nice. I have an idea for an immutable merge tree approach - stay tuned :-)

Saturday, April 12, 2014

Java 8, Eclipse Kepler, and Infinitest

When I updated my Mac to Java 8, Infinitest quit working. I've been hoping it would update and start working but it didn't happen.

I went looking and I found that the Eclipse Marketplace version of Infinitest is several years old. Had the project been abandoned?

I found an Infinitest web site which linked to the Github page. The readme there gave an update site of http://update.improvingworks.com/ and when I installed/updated from that site Infinitest started working again.

The web page gives an update site of http://infinitest.github.io which appears to point to the same version (5.1.110). I'm not sure which is the "correct" choice.

Now I'm just waiting for Proguard to be updated for Java 8.

Tuesday, March 18, 2014

Eclipse Crashing

I got back from six weeks travel and fired up Eclipse (4.3 Kepler on Mac OS X). Not surprisingly, there were updates so I said ok to install them and restarted when prompted. Except instead of restarting it crashed :-(  I tried restarting several more times but it kept crashing.

Rather than waste a lot of time figuring out what went wrong it was quicker to rename my old copy of Eclipse, download a new copy, and then import my plugins from the old install. (One of the nice things about Eclipse is that it's just a folder, and you can trivially have multiple installs.)

The only other thing I had to do was check which Java Eclipse was using (Eclipse > Preferences > Java > Installed JREs). I found it only listed an old version so I used the Search button to find the new one, made it the default, quit from Eclipse, and deleted the old version of Java to ensure nothing would use it.

You'd think that leaving a computer turned off would ensure that everything would work when you got back (barring hardware failures). But interrupting the modern day firehose of updates unfortunately often leads to problems. (Although in theory it shouldn't.)

Sunday, January 26, 2014

Java 8

I just finished reading Java SE 8 for the Really Impatient by Cay S. Horstmann. I'd recommend it as an introduction to Java 8 features. I've also read Cay's Scala for the Impatient, and used his Core Java books for reference. No doubt there will be other Java 8 books arriving soon. Pragmatic Programmers has Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions in beta. And Manning has Java 8 Lambdas in Action in Early Access. But unless I'm in a rush to learn about something, I don't usually go for beta or early access versions because I'd rather wait and read the final product.

Java 8 has seemed so far in the future that I haven't been thinking about it much. But I see it's supposed to be released in March, which isn't really that far off.

If I was starting a JVM project from scratch, I'd probably lean towards Scala. But for maintaining and improving the jSuneido Java code, I'm looking forward to Java 8, especially lambdas.

The current release version of Eclipse doesn't support Java 8, but there are early access releases available. Presumably support will be included in the next version of Eclipse. Intellij also has early support for Java 8.

Netbeans may be the best bet right now. Version 7.4 has Java 8 support, and the beta for Netbeans 8 is available.


Thursday, January 23, 2014

jSuneido Network Bug

beetle

Our installations run a scheduler as a client. (On jSuneido this could probably just be a thread on the server, but cSuneido is single threaded.)

On Windows, when we shut down the server, the scheduler will exit. On Linux the scheduler would hang.

I narrowed it down to a simple test case (on OS X, which seemed to behave like Linux)
  1. start the server
  2. start a client REPL
  3. from the client,execute: ServerEval("Exit")
  4. server exits
  5. client hangs (on Linux but not on Windows)
Strangely, if you killed the server (with Ctrl+C) then the client would get an exception instead of hanging.

I assumed that the client was blocking when it tried to read the response from the ServerEval (which never came because the server had terminated). And that on Linux, for some reason, it didn't recognize the socket was closed when it was blocked reading, although that didn't make a lot of sense.

I ran the client in the debugger and when it was hung, I paused it to see where it was. Sure enough it was in the socket read.

I searched the web trying to find anything related. There wasn't much, which was surprising. Most problems like this are documented by someone. 

But I did notice some of the code examples were checking the return value from channel.read and I wasn't. The documentation said read returns -1 when "the channel has reached end-of-stream". That didn't sound like channel closed to me, but it seemed like I should be checking it anyway.

And that was the problem. It wasn't actually blocking on the read, the read was returning -1, but I was looping until I read all the data, and that's what was hanging it.

To verify, I restored the code, made it hang, and checked the CPU usage - sure enough the client Java was at 100% (because it was in a tight loop calling channel.read over and over).

I'm still not sure why killing the server behaves differently from exiting normally. I guess the socket gets closed differently. (i.e. gracefully or not)

In hindsight it seems like an obvious bug in my code (not checking the return value). I think what threw me off was that it worked fine in Windows. Java is usually pretty good at hiding operating system differences, but not in this case.

Saturday, November 09, 2013

Hi-tech Travelling Blues

[On the train to Montreal.] After getting my fill of reading I pulled out my laptop to do a little programming. I clicked on Eclipse and got a message that I needed Java 6 and it wouldn't start. It helpfully offered to install one but I had no internet so that failed. This was working fine when I used it a few days ago so I assume an OS X update removed it. 

I like automatic updates but only if they don't blatantly break stuff. I've already gone through this reinstall of Java multiple times. It's not a big deal when you're connected. I understand the security issues, but that's pretty much all related to the browser plugin. I'm not sure why it has to keep removing Java entirely rather than just the plugin. 

I hunted around and found I still had several copies of Java 7 but I couldn't figure out how to get Eclipse to use them. I found a spot in the plist where you could specify a particular JVM but that didn't seem to help. I'm not sure why it was insisting on Java 6. 

There's probably a way to get it to work but I didn't have the patience and gave up frustrated. After all, I am on holidays, and this was supposed to be recreational programming!

But five minutes later I remembered that I had a computer within my computer - my Windows VM in Parallels, where I also have Eclipse. I got my laptop back out and fired up Parallels. Sure enough, that copy was out of reach of OS X updates and was still functional. (Windows updates haven't decided to arbitrarily remove Java, yet.) 

I don't normally do my Eclipse development under Windows on my MacBook so this copy of my source code was out of date but I could get the files I needed from OS X. 

So in the end I got operational. During our layover in Toronto I managed to install the missing JVM using Starbucks Wifi so I should be functional on OS X again. And just in case, I brought the Windows copy of my source code up to date. (Although for some reason it failed when I tried to pull the changes on OS X. Argh!)

Now I just have to figure out why Eclipse is using Java 6 instead of 7 ...

Tuesday, October 22, 2013

Updating Source Code to Java 7

I had this vague memory that NetBeans had a way to upgrade source code to Java 7, which seemed like a good thing to do. But when I searched on the web I couldn't find much. I did find stuff about the IDE giving hints and fixes individually in the IDE but nothing about mass changes. (Which is partly what prompted me to write this.) I also looked for a way to do it with Eclipse but didn't find anything.

So I downloaded the latest NetBeans (7.4) and hunted through the menus. I found Refactor > Inspect and Transform which has a Configuration choice for Convert to JDK 7.

TIP: Set your tab and formatting (e.g. switches) preferences before you run Inspect and Transform. It didn't seem to work correctly when I changed the preferences while Inspect and Transform was open.

It found the following applicable changes in my code:

  • Use diamond inference
  • Convert to switch over strings
  • Convert to try-with-resources
  • Replace with multicatch
  • Replace with multicatch catching specific exceptions

The majority were diamond inference. There would have been even more, but I already used Guava's helpers such as Lists.newArrayList which avoid repeating the generic types.

Convert to try-with-resources didn't merge surrounding try-catch's but there were only a few of these so it was easy to fix them manually.

I wasn't too sure about the last item - replacing catching general exceptions with catching multiple specific exceptions. It seemed like it wouldn't catch everything it did before so I didn't accept those changes. (unchecked them)

But when I clicked on Do Refactoring I got a little "Refactoring" window that was blank. I assumed it was working and left it for a while. But it never went away, and when I tried to close it I couldn't. So I exited out of NetBeans (with no problems) and tried it again. This time nothing happened (except the Inspect and Transform window closed). I thought maybe it was finished but nothing had changed. I ran it again and got only a few of the issues and Do Refactoring worked (on those few). Next time I ran it, I got the long list again. I finally noticed the error marker in the bottom right. I submitted the error and it appears to be a known bug :-(

I ended up doing one package at a time and that seemed to work fine.

Monday, October 14, 2013

More Hamcrest Hassles

The Hamcrest library is very useful. So useful that other useful libraries, like JUnit, include pieces of it. And then JUnit is so useful that Eclipse includes its own copy.

Not surprisingly, having multiple copies, of different versions, of different subsets, with some signed and some not, results in numerous problems. For accounts of my own experiences see: Upgrading to Eclipse Kepler 4.3, Eclipse Hamcrest Runaround, and I Give Up. Searching the web will find lots of other people with similar problems.

Once again, I had thought I had solved this by not using Eclipse's copy of JUnit. Everything has appeared to be working fine for months.

Until I was adding a test today. In good TDD style, I started by adding the test before making the change. It failed, as expected, but not with the error message I expected. I got:

java.lang.NoSuchMethodError: org.hamcrest.Matcher.describeMismatch

I thought maybe it was because I was using org.junit.Assert.assertThat instead of org.hamcrest.MatcherAssert.assertThat - but switching it didn't fix anything.

I also noticed that Eclipse was marking org.hamcrest.CoreMatchers.is as deprecated. Maybe it should be org.hamcrest.Matchers.is? Nope, still deprecated. I eventually found a comment on Stack Overflow that there are three overloads of "is" and it's only the class one that is deprecated, not the one I'm using. So I guess I just live with that warning :-(

Another comment on Stack Overflow mentioned that Mockito also included it's own version of Hamcrest. Hmmm... I don't think I was aware of that before.

One of the suggested solutions to this problem is to rearrange the order of the jar files on the class path. I had tried that previously without any success. But I was only moving JUnit and Hamcrest, since I wasn't aware that Mockito was also involved.

I find the Eclipse project properties Java Build Path a little confusing. There's a Libraries tab that lists all the jars alphabetically and you can't change the order. And then there's an Order and Export tab where you can change the order. I'm not sure why two tabs are needed. There's also a Referenced Libraries in the Package Explorer that does show the actual order, but doesn't let you change it.

I moved Mockito down in the list so it was below Hamcrest (JUnit was already below) and sure enough, that solved the problem.

Except now I had a different error in another test. That one turned out to be because I was using "is" with a class. Maybe it's deprecated in one of the versions/copies of Hamcrest but removed in the version of Hamcrest I'm explicitly including? Luckily this was simple to fix by just changing it to instanceOf.

It looks like you can download separate jars for Mockito which would let you leave out it's copy of Hamcrest. Except that it appears to be using a different version of Hamcrest (1.1) from the one I'm using (1.3). I have no idea if that would cause problems, but since it's currently not broken, I don't think I'll try to fix it!

Probably someone out there will tell me that I should be using Maven to manage dependencies, and maybe I should. But I'm not so sure that would eliminate these problems. I see several comments on the web about the same issues when using Maven.

Sunday, October 13, 2013

jSuneido GUI


What's special about this screenshot of the IDE isn't what's visible.

It's that this is running on jSuneido! (the Java implementation of Suneido).

Up till now jSuneido has only been the server side. The only "UI" it had was a command line REPL.

But in the long run, I'd rather not support two implementations of Suneido. It would be nice if we could just use jSuneido since it's a better implementation.

Suneido's user interface is Win32 based and implemented with the DLL interface. None of the UI is built into the exe, it's all Suneido code in stdlib. (Other than a few support functions.)

Ideally, we'd switch to a portable GUI, but that's a huge job and would likely mean a bunch of changes to our application code.

So we decided to see if we could implement a Windows DLL interface in jSuneido and get Suneido's existing GUI to run on it.

One of Suneido's early programmers , Victor Schappert, returned to us and worked on this project. Thanks Victor!

As you can see, it's far enough along to run most of the IDE. but we still have a few things left to do like the COM interface and SuneidoAPP interface to the IE browser component.

As usual, the code is in version control on SourceForge. The JSDI project in Mercurial (Hg) contains a support dll for jSuneido (written in C++) that jSuneido talks to via JNI. A pre-built version of jsdi.dll is included in the jSuneido project.

Monday, August 12, 2013

A Recurring Lack of Assertiveness

We recently ran into a problem with jSuneido - when you loaded a certain customer's dumped database it would load successfully but then fail the consistency checks.

After looking at where and how it was failing (a negative size value), I figured the problem was probably that the database had a single table larger than 2 gb and I was using a 32 bit int to store the size of commits so it was wrapping around. Normally commits wouldn't be anywhere near that large, but when bulk loading each table is written as a single commit.

I felt pretty good about finding the problem so quickly and easily.

I put asserts into the code to confirm that this was the problem. But they didn't fail. Hmmm... maybe I put them in the wrong place. I added more asserts in other places. They still didn't fail.

So I fell back on the age old debugging method of inserting print's. Of course, it's a big database with a lot of tables so there was lots of output. I skimmed through it and couldn't find a table bigger than 2gb.

So much for finding the problem quickly and easily!

The next day, at home, I continued working on it, partly just trying to remember how the database code works! On the positive side, I updated some comments and diagrams while I was at it.

Eventually, I ended up full circle, finding that there was indeed a table bigger than 2gb and my original guess about the problem was correct! Argh! (I'd missed it when I skimmed through the prints.)

The problem was that I didn't have assert's enabled, which is why the asserts I added didn't fail. I've been burnt by this before - see Don't Forget to enable Java assert and Burnt by Java assert Again. You'd think I would learn.

Part of the problem is the way Eclipse works. You can set options on JRE's, but when you update to a new version of Java, then you have to remember to set the options again (which I had forgotten to do, both at work and at home). It's too bad there isn't a way to set options that are common to all JRE's.

You can also set options in Eclipse launch configurations, but I have a ton of them, and again (AFAIK) there isn't a way to set default options that are common to all launch configurations.

I thought I had good defenses in place for this. I have a test which confirms that assert is enabled. But I'm using Infinitest to run my tests automatically and it must enable asserts itself. So unless I run the test manually, it's useless for confirming that I have asserts enabled.

I also enable asserts programmatically in the start-up code. But while I was testing I was running specific classes and bypassing the start-up code.

I'm not sure what else I can do to defend against this. Any suggestions?

Thursday, August 01, 2013

Upgrading to Eclipse 4.3 Kepler

Another relatively smooth upgrade.

I downloaded the Eclipse IDE for Java Developers (rather than Standard) since I don't do any plugin development.

See Top 10 Eclipse Kepler Features

I did have one problem on Mac OS X - when you try to run Eclipse you get an error that it is "damaged and can't be opened". I'd run into this before. It's a known issue which, for some reason, the Eclipse developers have closed as "RESOLVED NOT_ECLIPSE" i.e. not their problem. However, as one of the commenters points out, other apps don't have this problem, so it's obviously something Eclipse is doing different if not technically "wrong". It's relatively easy to work around by doing:

xattr -d com.apple.quarantine Eclipse.app/

I imported my plug-ins from my previous Eclipse 4.2 Juno and they all came across without any problems. Even Mercurial still seems to be functional. Here's the list of plug-ins I'm currently using:
  • Bytecode Outline
  • EclEmma Java Code Coverage
  • Checkstyle
  • FindBugs
  • Infinitest
  • MercurialEclipse
  • Metrics plugin for Eclipse
The other problem I ran into is also a known issue - conflicts (giving SecurityException) between the hamcrest-core in the Eclipse copy of JUnit, and hamcrest-library that I'm adding for additional matchers. At first I thought it was because the new version of Eclipse came with a newer version of JUnit, so I upgraded my hamcrest-library to match. But that didn't solve the problem.

I knew I'd run into this before so I went to my blog to see how I'd resolved it. I found Eclipse Hamcrest Runaround and I Give Up but nothing about how I eventually got it to work. It's funny how my blogs have become my external memory and I'm unhappy if I forget to record something.

After searching the web and messing around I realized the easiest solution is just to remove the Eclipse JUnit from the build path and have my own junit, hamcrest-core, and hamcrest-library. That seems to solve the problem.

See also:
Although I don't feel like I'm usually recording much useful information, these upgrade posts are some of my most frequently visited. That may just be because they come up in searches.