tag:blogger.com,1999:blog-28542707331966208932024-02-22T09:17:47.542-05:00Sysadmin Still SurvivingWhere every draft is a first draft...Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.comBlogger170125tag:blogger.com,1999:blog-2854270733196620893.post-57743760911127538482018-07-25T18:54:00.001-04:002018-07-25T18:54:08.516-04:00Our First Official Trike RideI've been focusing recently on weight and health. Haven't really been talking about it much for a few reasons (except on the Geeking After Dark podcast...) but the biggest recent development was the acquisition of recumbent trikes.<br />
<br />
It had been quite an adventure in itself (dear Customs and Border Protection: eff you) but my son and I finally got our modified <a href="http://avenuetrikes.com/" target="_blank">First Avenue trikes</a>!<br />
<br />
At first we primarily rode in our driveway; we made 6 circuits the first day, then 5 the next. It was our basic shakedown, learning the handling on the trikes and getting a feel for the brakes and seating. We also needed to build familiarity with the shifting, because our trikes are equipped with Nuvinci hubs instead of relying on derailleurs for changing distinct gears.<br />
<br />
(By the say, the Nuvinci hubs are utterly amazing...expensive, but amazing.)<br />
<br />
There was a lull in riding because Little Dude had a friend over for a few days, and after that, weather decided it didn't want to cooperate. But the weekend rolled up, the rain broke, and we were determined to try riding on the road!<br />
<br />
We broke down the trikes; in order to rack them, the seats, trunk bags and accessories have to be removed. Once the trikes were secured to the car rack, we got the equipment fit into the back seat of the car and filled our insulated water bottles (flavored with tablets that add caffeine, vitamins, and some refreshing...somethings...to keep you from feeling like you want to pass out while exercising) before trekking to a valley area about half an hour from our home.<br />
<br />
The area we rode is near my childhood home; I was relatively familiar with the area due to working at a historic site and attending the church in that valley area. Other than those two things the area is populated by farmers and was mostly a closed, paved loop.<br />
<br />
The closed circuit path meant that it was mostly local traffic, but it was decently paved as a two-lane road (without a center line, though; it was kind of narrow in places, but I figured the sparse traffic would make this a nice introduction to road riding.)<br />
<br />
In my head I picture the path as an elongated 2-dimensional Pokeball; the top half is a higher elevation, and the middle of the circuit is bisected by a packed dirt/gravel road, which would have cut the travel in half and avoided having to climb the hill to the upper part of the road circuit.<br />
<br />
I had forgotten about that bisecting road; my father reminded us about it when asking about our route. I have been exercising a little using a pedaling machine under my desk (not a perfect simulation of a recumbent bike, but better than nothing) for several months as well as some basic workout routines from a fitness specialist. My son hasn't been working out; aside from our rounds on the driveway (which, to be fair, is about 600 feet long and rises a little under 20 feet from entrance to parking flat) riding the recumbent on the road was kind of cold turkey exercise for him.<br />
<br />
We de-racked the trikes and re-equipped them, mounting the trunk bags, water bottles and seats. We sat down, adjusted mirrors, and I explained the proposed route along with reiterating my warnings about watching for traffic and staying to the right (I was perpetually anxious about our road riding since he is not experienced with driving, let alone traveling on the road.)<br />
<br />
"Little Dude, we have two options. The first is the one I was first thinking of...it's paved, but it's longer, and there's a big hill climb to deal with. The other is the one Grandpa mentioned; it's a packed gravel road that cuts travel in half and avoids the hill, but it's going to be bumpy. Which one should we take?"<br />
<br />
He thought about it for a moment and said, "We'll take the long way."<br />
<br />
I was so proud of him!<br />
<br />
"Okay. Ready?...let's go!"<br />
<br />
I launched Strava, an app on my iPhone for tracking our exercise stats, and we started our ride. The first part was relatively flat; some small inclines, but nothing we couldn't really handle. Little Dude was slower due to not having worked out and developed the leg muscles needed for the leg presses that pedaling recumbent trikes model. I did get ahead of him at times, but I kept an eye on the mirror and if he started to fall pretty far behind I'd pull off and wait for him to catch up.<br />
<br />
We did pass some family friends who were out for a walk with their dog. I didn't know they even had this dog...I told my parents that I didn't realized they had a pet bear, because this thing is the size of a polar bear cub. I mean, it's HUGE. And fluffy. It was a giant white furball the size of a dwarf horse, and it was at least as tall as my head was positioned on the recumbent trike.<br />
<br />
"Oh, that's Rufus," my parents said.<br />
<br />
"Rufus. They named this bear-sized dog Rufus." I had trouble wrapping my head around the juxtaposition of a pet named Rufus that looked big enough to pull a sled of kids.<br />
<br />
We said hello as we rolled by them and the dog just sort of gazed quizzically at the two overweight riders on the weird tadpole machines; I was thankful it didn't decide we were invading its space and attack or bark, as I was certain the force of the bark might blow us into the corn field.<br />
<br />
At one of the pause points, I pointed out that we were approaching the hill.<br />
<br />
"Last chance. We can turn here and take the halfway road, or we continue up that," I said as I gestured towards the visible escalation in pavement.<br />
<br />
Little Dude rested a few minutes, took a swig of water and said he was ready to go up the hill.<br />
<br />
Oh gawd...the hill was tougher than I thought. I had to stop a few times on the incline, as my muscles would hit the point of failure. If I was hitting that, I knew Little Dude was having it harder than I was. I could see in my mirror that he was stopping along the road, but after a few minutes, I'd again see his feet pumping the pedals as he made progress forward again.<br />
<br />
We paused several times. I didn't mind; I was amazed Little Dude, who was not accustomed to this kind of physical work, was still soldiering on. Forward progress was forward progress!<br />
<br />
He caught up to me. I pointed at the house in front of us where the road sharply curved into the grove of trees; "We're not far now. Once we hit that curve, we not only have shade, but the road doesn't keep climbing like this."<br />
<br />
He didn't really seem to believe me, but at this point we didn't have much choice but to continue on. "Ready?"<br />
<br />
We kicked forward again. Eventually we took the curve and stopped in a driveway where we didn't have to worry about traffic and could enjoy the slight breeze.<br />
<br />
Little dude was red in the face and had rivulets of sweat dropping from his head and darkening his shirt. "I can't feel my legs, Dad," he said.<br />
<br />
"You mean they feel like something is wrong, or they're tired?"<br />
<br />
"I think they're just tired."<br />
<br />
Dude is sensitive to dehydration, and it was hot today. He was drinking from his water bottle but I knew that it would be running low by now. We rested a bit in the shade, hands laced behind our heads to allow our lungs to expand wider and take in more oxygen, before I asked him if he was feeling better.<br />
<br />
"I just need a minute or two," he said.<br />
<br />
"Do you want to call it quits," I said. I figured I could run ahead and get the car, pack up my trike and return for him. He was looking really tired and I was a little concerned about how red his skin had become.<br />
<br />
Again he thought a moment before replying, "I'm not going to quit, Dad!"<br />
<br />
I can't really describe the pride I felt, seeing him push through his aches and sore legs to keep moving forward on his first real ride on the trike. "I'm not going to quit." He was not taking the easy way out!<br />
<br />
"Okay. We'll keep going. Tell me when you're ready!"<br />
<br />
We had made it up the steepest, longest part of the ride. We had a relatively flat ride before hitting the downward portion of the trip; our bike computers registered a top speed of a little over 28 miles per hour (or, as he put it, "THAT WAS AWESOME!") At the peak speed, we zipped by an older couple sitting on their porch. If Rufus thought we were strange, I couldn't imagine what this couple thought of the two overweight guys on these weirdly configured wheeled lawn chairs were doing as the tires hummed along the pavement and the pilots whooped with glee at the air whipping through our hair.<br />
<br />
We pulled off the road and stopped next to the car. Despite feeling pretty good, standing up proved to be a challenge, as my blood pressure felt like it was dropping dramatically as I stood upright for the first time in over an hour. Little Dude asked for a few minutes before having to peel himself out of his seat and disassemble the trike for racking.<br />
<br />
I slowly released the pins and quick releases on my trike that held the seat to the frame, disconnected accessories and bags, then steeled myself to get the trike lifted onto the rack. "Take your time, Dude," I said. "You have a lot to be proud of."<br />
<br />
I had packed protein bars for us with the intention of stopping at a picnic area on the path to rest, but as our water ran low and there was a threat of impending rain, I nixed the idea. Little Dude had scooped up an empty Red Bull can with the intention of giving it to his Grandfather for recycling and deposit redemption; their house was on the way home, so I figured we'd stop, get more water and have our protein bars while visiting.<br />
<br />
Strava said we spent 42 minutes of actual travel time (it pauses automatically if GPS doesn't show us moving) and had climbed 263 feet over a trip of 4.28 miles. I don't think that was bad at all for a first trip out!<br />
<br />
We've started learning a few things about the trip. We remembered to mount the blinking red lights to increase our visibility...but forgot to turn them on (halfway through the trip I activated them.) We also brought helmets, but forgot to actually wear them, which didn't matter quite as much since trikes are a little harder to tip over and state law didn't require the helmets for people our age. If there were more traffic, I'd have turned around to grab them. As the situation was...I let it go, figuring we might feel a little cooler with a breeze as the sun was beating down rather hard for the first legs of the journey.<br />
<br />
I think we also need to have more water-carrying capability. Next time we're in the shop, I think I'll ask them to install an additional water bottle cage on each trike and we'll go shopping for another insulated bottle. In the meantime I ordered a set of pannier bags for my trike so I'll have increased cargo capability, then I'll see if I can find something that can hold water without fear of spillage in the panniers.<br />
<br />
The weather has once again decided to work against us...we've had days of rain, complete with a constant flash flood watch culminating now into a flash flood warning...so we haven't been out riding again. But we do plan to head out again this weekend. The weather is predicted to break and I've been scoping out a possible bike path to try an hour away. Little Dude is looking forward to the next ride, and I have to admit that I'm more than a little anxious to hit the pavement again as well.<br />
<br />
I recounted our trip to my wife, and we were both extremely proud of Little Dude and how hard he worked to keep going. He felt bad about the slower pace of the trip, feeling that he was holding us back...but I told him, truthfully, that there was no reason to feel bad. He was working on developing the leg muscles, he wasn't used to riding, and we both had work to do to get more proficient in riding. I had no problem with our pace...the important thing was we did it, and we pushed on. He didn't take the easy road or cut corners.<br />
<br />
And more than that, he was still looking forward to the next ride! For now, we're keeping an eye on the forecasts and will have the trikes ready to rack. Allons-y!Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-49117130209437113652018-06-17T18:01:00.000-04:002018-06-17T18:01:52.404-04:00Why the Blog Hiatus?In the past few years (wow, has it really been a few years already?) things have really changed for me. We (meaning my family) have had some upheavals and trials. For a while it seemed like there was no hint of good news without a wave of misfortune following close behind.<br />
<br />
I'm the type of person with a brain wired for routine. I managed to keep some constants, such as recording the Geeking After Dark podcast with regularity, which helped maintain the illusion of control over my own life. I had a friend with contacts that managed to help me find employment after going back to rural PA. And of course there is the constant barrage of terrible that is the state of our country under the current administration, and worse, the support the people of this area show for the ideology of said administration, which does little to quell fear of what other people will do if given the opportunity to show their "true colors" without being held accountable for their actions.<br />
<br />
I didn't realize it at the time, but the combination of stresses were taking a toll. Both my physical and mental health were in decline until it reached a point where I couldn't keep turning a blind eye to the situation.<br />
<br />
Back in the beginning of January I had a "coronary incident;" my wife took me to the ER in the wee hours of the morning and I spent the next day having tests run (ever have a catheter run through your arm to see if you have a blockage to the heart? Highly not recommended.) It turned out to not be a heart attack, but pericarditis, in which an inflamed sac of tissue around the heart swells and imitates the first symptoms of a heart attack.<br />
<br />
Ironically, I had already agreed to take the preliminary steps for a more thorough admission to a weight clinic at the hospital, and my first appointment was three days after my discharge from the ER.<br />
<br />
I've had appointments to address a wide range of issues from the stresses and...while I hesitate to call it this...the psychological trauma that has been building up over the previous two years. I have doctors coordinating in fields across endocrinology through bariatrics to try making progress on my health. Some of this I've already brought up in the podcasts, some of it I never really talked about because I didn't think it was worth mentioning.<br />
<br />
I've been working a lot on my programming in Golang at my new job position, and thoroughly enjoying it. Usually I'd blog some thoughts or tidbits about what I learned. Then one day I looked at this blog and realized I hadn't written anything in months...I couldn't believe how much time had passed while I was in some kind of mental fog.<br />
<br />
That isn't to say I haven't made progress. Since January 10th, I've lost 106 lbs. I've gone off several medications while cutting back on others.<br />
<br />
Doctors have not only been forcing me to work on eating "healthy" stuff like...vegetables (yuck) but to exercise more. My current job is heavy on the sitting-at-the-desk duties, so I've been using an under-desk pedaling machine to work my legs. As summer approached I started looking at an alternative exercise that was tolerable for my taste and lifestyle; I started investigating recumbent biking. As I type this, there are two recumbent trikes on their way to an almost local recumbent dealer earmarked with my (and my son's) names.<br />
<br />
While treating some aspects of my various diagnoses had made progress, one side effect has been a diminished passion for side projects. My work on side programming projects has slowed down, but I still have an idea bouncing in my head that I'm thinking of trying to explore.<br />
<br />
So, why the blog hiatus? It boils down to the stresses of the past couple of years being addressed, and not realizing how much time had passed. I haven't disappeared. Nothing tragic happened. I've simply changed my focus for a bit. As a result, my health, so far, has been slowly improving, and I've been nursing a new obsession with recumbent triking.<br />
<br />
If things continue to go as I hope, I'll get back to blogging more. I'll continue programming and making progress on the learning front. And maybe, just maybe, I'll continue to improve my diet and exercise lifestyle changes with the help of a new recumbent trike!<br />
<br />Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-91464613591583926722018-03-12T17:24:00.000-04:002018-03-12T17:24:14.888-04:00Golang: Is The Mutex Is Locked, And Finding The Line Number That Did ItQuick summary of the situation, giving enough details to highlight the problem but not giving proprietary information away...<br />
<br />
I have a program that queries a service which in turn talks to a database. The database holds records identified by unique rowkeys. I want to read all of the records, as far as the database knows of their existence, which I can get through an API call, iterating in discrete steps.<br />
<br />
The utility I created pulls a batch of these keys, then iterates over them one by one to determine if I want to make a call to the service to pull the whole record (I don't need to if the key was already called before or previously analyzed on a previous run of the program.)<br />
<br />
Seems relatively simple, but this is a big database and I'm going to be running this for a long time. Also, these servers are in the same network, so the connections are pretty fast and furious...if I overestimate some capacity, I'm going to really hammer the servers and the last thing I want to do is create an internal DDoS.<br />
<br />
To that end, this utility keeps a number of running stats using structs that are updated by the various goroutines. To keep things synced up, I use an embedded lock on the structs.<br />
<br />
(Yeah, that's a neat feature...works like this:)<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">type</span> <span style="color: white;">stctMyStruct</span> <span style="color: #fb660a; font-weight: bold;">struct</span> <span style="color: white;">{</span>
<span style="color: white;">sync.Mutex</span>
<span style="color: white;">intCounter</span> <span style="color: #cdcaa9; font-weight: bold;">int</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
After that, it's a simple matter of instantiating a struct and using it.<br />
<br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">strctMyStruct</span> <span style="color: white;">stctMyStruct</span>
<span style="color: white;">strctMyStruct.Lock()</span>
<span style="color: white;">strctMyStruct.intCounter</span> <span style="color: white;">=</span> <span style="color: white;">strctMyCounter.intCounter</span> <span style="color: white;">+</span> <span style="color: #0086f7; font-weight: bold;">1</span>
<span style="color: white;">strctMyStruct.Unlock()</span>
</pre>
</div>
<br />
Because the utility is long-running and I wanted to keep tabs on different aspects of performance, I had several structs with embedded mutexes being updated by various goroutines. Using timers in separate routines, the stats were aggregated and turned into a string of text that could be printed to the console, redirected to a file or sent to a web page (I wanted a lot of options for monitoring, obviously.)<br />
<br />
At some point I introduced a new bug in the program. My local system was relatively slow when it came to processing the keys (it's not just iterating over them...it evaluates them, sorts some things, picks through information in the full record...) and when I transferred it to the internal network, the jump in speed accelerated exposure of a timing issue. The program output...and processing...and web page displaying the status of the utility...all froze. But the program was still running, according to process monitoring.<br />
<br />
I first thought it was a race condition...something is getting locked and not releasing it. But how can I tell if a routine is blocked by a locked struct? Golang does not have a call that will tell you if a mutex is locked, because that would lead to a race condition. In the time it takes to make the call and get the reply, that lock could have changed status.<br />
<br />
Okay...polling the state of mutexes is out of the question. But what <i>isn't</i> out of the question is tracking when a request for a lock is granted.<br />
<br />
First I changed the struct to have an addressable member for setting the state of the lock.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">type</span> <span style="color: white;">stctMyStruct</span> <span style="color: #fb660a; font-weight: bold;">struct</span> <span style="color: white;">{</span>
<span style="color: white;">lock</span> <span style="color: white;">sync.Mutex</span>
<span style="color: white;">intCounter</span> <span style="color: #cdcaa9; font-weight: bold;">int</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
Next I created some methods for the struct to handle the locking and unlocking.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">(strctMyStruct</span> <span style="color: white;">*stctMyStruct)</span> <span style="color: white;">LockIt(intLine</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">)</span> <span style="color: white;">{</span>
<span style="color: white;">chnLocksTracking</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Requesting lock to strctMyStruct by line "</span> <span style="color: white;">+strconv.Itoa(intLine)</span>
<span style="color: white;">tmElapsed</span> <span style="color: white;">:=</span> <span style="color: white;">time.Now()</span>
<span style="color: white;">strctMyStruct.lock.Lock()</span>
<span style="color: #fb660a; font-weight: bold;">defer</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="color: white;">chnLocksTracking</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Lock granted to strctMyStruct by line "</span> <span style="color: white;">+</span> <span style="color: white;">strconv.Itoa(intLine)</span> <span style="color: white;">+</span> <span style="color: #0086d2;">" in "</span> <span style="color: white;">+</span> <span style="color: white;">time.Since(tmElapsed).String()</span>
<span style="color: white;">}()</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">(strctMyStruct</span> <span style="color: white;">*stctMyStruct)</span> <span style="color: white;">UnlockIt(intLine</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">)</span> <span style="color: white;">{</span>
<span style="color: white;">chnLocksTracking</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Requesting unlock to strctMyStruct by line "</span> <span style="color: white;">+strconv.Itoa(intLine)</span>
<span style="color: white;">tmElapsed</span> <span style="color: white;">:=</span> <span style="color: white;">time.Now()</span>
<span style="color: white;">strctMyStruct.lock.Unlock()</span>
<span style="color: #fb660a; font-weight: bold;">defer</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="color: white;">chnLocksTracking</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Unlock granted to strctMyStruct by line "</span> <span style="color: white;">+</span> <span style="color: white;">strconv.Itoa(intLine)</span> <span style="color: white;">+</span> <span style="color: #0086d2;">" in "</span> <span style="color: white;">+</span> <span style="color: white;">time.Since(tmElapsed).String()</span>
<span style="color: white;">}()</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
LockIt() and UnlockIt() methods are now added to instances of stctMyStruct. When called, the function first sends a string into a channel with a dedicated goroutine on the other end digesting and logging messages; the first acts as a notification that the caller is "going to ask for a change in the mutex."<br />
<br />
If the struct is locked, the operation will block. Once it is available, the function returns, and in the process runs the defer function which sends the granted message down the channel along with the elapsed time to get the request granted.<br />
<br />
How does it know about the line number?<br />
<br />
There's actually a library function that can help with that; my problem is that it returns too much information to not be a little unwieldy. To get around that, I created a small wrapper function.<br />
<br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">GetLine(</span><span style="color: white;">)</span> <span style="color: #cdcaa9; font-weight: bold;">int</span> <span style="color: white;">{</span>
<span style="color: white;">_,_,intLine,</span> <span style="color: white;">_</span> <span style="color: white;">:=</span> <span style="color: white;">runtime.Caller(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">intLine</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
If you look at the documentation you can get the specifics of what is returned, but Caller() can unwind the stack a from a call by the number of steps you use as an argument and return the line number, package/module, etc...in my particular case I'm using one source file so I only needed the line number.<br />
<br />
Using this, you can insert function calls to lock and unlock the structs as needed. I added the methods to each struct that had a mutex or rwmutex. Using them is as simple as:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: white;">strctMyStruct.LockIt(GetLine())</span>
</pre>
</div>
<br />
This solution provided a way to trace what was happening, but there is a performance cost. Defer() adds a few fractions of a second each time it's called and I used a lot of locks throughout the program which added up to a significant performance hit. Using this technique is good for debugging, but you have to decide if you want to incur the overhead or find a way to compensate for it.<br />
<br />
So what was my lock issue?<br />
<br />
I set the goroutine monitoring the locks to dump information to a file and traced the requests vs. granted mutex changes. There was a race condition in a function I used that summarized aggregated information; A lock near the beginning of the summary was granted, and while pulling other information, it requested another lock. The second one was an operation on a struct that was held by a process waiting to get a lock on what was being held by the beginning of the summarize function.<br />
<br />
It was a circular resource contention. Function A held a resource that Function B wanted, and Function B had a resource function A wanted. The solution was to add more granular locking, which added more calls but in the end meant (hopefully) there would be only one struct locked at a time within a given function.<br />
<br />
Lesson learned: when using locks, keep the calls as tight and granular as possible, and avoid overlapping locks as much as possible or you may end up with a deadlock that Go's runtime wouldn't detect!Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-33594470133428197592018-03-02T17:38:00.000-05:002018-03-02T17:38:20.140-05:00On The Importance of Planning A ProgramI'm not a professional programmer.<br />
<br />
I'm not sure I could even qualify as a junior programmer.<br />
<br />
What I have been doing is programming at a level that is above basic scripting, but below creating full applications. I've been churning out command line utilities for system activities (status checking and manipulating my employer's proprietary system, mostly, along with a bevy of Nagios plugins) with the occasional dabbling into more advanced capabilities to slowly stretch what I can accomplish with my utilities.<br />
<br />
That said, I've been trying to reflect on my applications after they've been deemed "good enough" to be useful. In a way, I try running a self-post-mortem in hopes of figuring out what I think works well and what can be improved.<br />
<br />
I was recently in a position where I had to create a utility, then months later, got permission to rewrite it, giving me a unique opportunity to take an application that had a specific set of expectations for output and let me refactor its workflow in hopes of improving performance and information it gathered in the process.<br />
<br />
For reference, the 10,000 foot view is that I have a large set of data from a large database, and we wanted to dump the contents of that database, using an intermediate service providing REST endpoint API calls, to save each record as a text file capable of being stored and uploaded in another database. A vendor-neutral backup, if you will...all you need is an interpreter that is familiar with the text file format and you could feed the contents back into another service or archive the files offsite.<br />
<br />
It seems like this would be a small order. You have a database. You have an API. The utility would get a set of records, then iterate over them and pull records to save to disk.<br />
<br />
Only...things are never that simple.<br />
<br />
First, there's a lot of records. I realize "a lot" is relative, so I'll just say it's in the 9 digits range. If that's not a lot of records to you, then...good on you. But when you reach that many files, most filesystems will begin to choke, so I think that qualifies as "a lot."<br />
<br />
That means I have to break up the files into subdirectories, especially if the utility gets interrupted and needs to restart. Otherwise filesystem lookups would kill performance. Fortunately there's a kind of built-in encoding to the record name that can be translated so I can break it down into a sane system of self-organizing subdirectories.<br />
<br />
Great! Straightforward workflow. Get the record names. Iterate to get the record contents. Decode the record name to get a proper subdirectory. Check if it exists. If not, save it.<br />
<br />
Oh, there are some records that are a kind of cache...they are referred to for a few days, then drop out of the database. No need to save them.<br />
<br />
Not a problem, just add a small step. Get the record names. Iterate to get the record contents. Check if it's a record we're not supposed to archive. If we are, decode the record name to get a proper subdirectory. Check if it exists. If not, save it.<br />
<br />
During testing, I discover there are records whose records cannot be pulled. The database will give me a record name but when I try to pull them, nothing comes back. That's odd, but I add a tally of these odd names and a check is inserted for non-200 responses from the API calls.<br />
<br />
Then there are records that I can't readily decode. They're too short and end up missing parts for the decoding process. At first I write them off as something I have to tally as an odd record in the logs, but discover that when I try pulling them, the API call returns an actual record. I take this to the person who has institutional knowledge of the database contents and after examining the sample of records, states that it looks like the records were from an early time in the company history.<br />
<br />
Basically, there's a set of specs that current records should follow, but there are records from days of yore that are valid but don't follow the current specs.<br />
<br />
So there are records that should be backed up...but don't follow the workflow, where I have functions that check for record validity through a few tests before going through the steps of making network calls and adding to the load on the servers acting as intermediaries for the transfer. To fix this, I insert a new pathway for processing those "odd" records when they're encountered, so they end up being queried and translated and, if they are a full record, saved to an alternative location. The backups are now separated into the set of "spec" records and another "alternative" path.<br />
<br />
The problem is that this organic change cascades into a number of other parts of the utility; my tally counts for statistics are thrown off. The running list of queued records to process have to take into account records that are flowing into this alternative path. Error logging, which also handled some tallying duties since it was an end-of-life for some of the records to be processed, weren't always actually errors but actually a notification that something had happened during the process that was helpful during tracing and debugging but a problem when it would mark certain stats off before the alternative record was processed.<br />
<br />
That one organic change in the database contents during the history of the company had implications that totally derailed some of the design of my utility that took into account only the current expected behavior.<br />
<br />
In the end, I lost several days of debugging and testing when I introduced fixes that took into account these one-offs and variations. What were my takeaways?<br />
<br />
It would be simple to say that I should have spend some days just sketching out workflows and creating a full spec before trying to write the software. The trouble is that I didn't know the full extent to which there were hidden variations in the database; the institutional knowledge wasn't readily available for perusing when it resides in other people's heads, and they're often too busy to try coming up with a list of gotchas I could watch out for in making this utility.<br />
<br />
What I really needed to do was create a workflow that anticipated nothing going quite right, and made it easy to break down the steps for processing in a way that could elegantly handle unexpected changes in that workflow.<br />
<br />
After thinking about this some more, I realized that it was just experience applied to actively trying to modularize the application. The new version did have some noticeable improvements; the biggest involved changing how channels and goroutines were used to process records in a way that cut the number of open network sockets <i>dramatically </i>and thus reduce the load on the load balancers and servers<i>.</i> Another was changing the way the queue of tasks was handled; as far as the program was concerned, it was far simpler to add or subtract worker routines in this version than the previous iteration.<br />
<br />
I'd also learned more about how to break down tasks into functions and disentangle what each did, which simplified tracing and debugging. Granted, there are places where this could still have been improved. But the curveballs introduced as I found exceptions to the expected output from the system, for the most part, just ate time as I reworked the workflow and weren't showstoppers.<br />
<br />
I think I could have definitely benefited from creating a spec that broke tasks down and figured out the workflow a bit better, along with considering "what-ifs" when things would go off-spec. But the experience I've been growing in my time making other utilities and mini-applications still imparted improvements. Maybe they're small steps forward, but steps forward are steps forward.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-52115129909610708502018-01-13T11:50:00.002-05:002018-01-13T11:50:45.282-05:00Regulations and Dieting (and Surgery)This is a few thoughts that involve something common in the new year; dieting. Well, tangentially diet related.<br />
<br />
Part of the issues I've had cascade down in the past few months...thanks life!...has led to appointments with the rather new bariatric unit at the local hospital unit. They take a whole-in approach of using a team of nutritionists, fitness experts, gastric surgeons, psychologists...the whole nine yards...to create a program with support system for patients.<br />
<br />
Part of the intake process meant reviewing your history. This is where I learned something nifty (beyond this machine that weighs you while zapping you with a current that measured all sorts of density information regarding the different kinds of body fat and densities in your body to come up with a profile of good and bad stuff in your body).<br />
<br />
They asked about my past history and I told them about the gastric bypass procedure I underwent many years ago...I believe it was around 2009. April. Somewhere in there. My memory is fuzzy.<br />
<br />
At the time, the local hospital system didn't really have a bariatric unit. While they very much seemed to support the idea that if you're fat, most of your illnesses and afflictions were weight-based and you needed to lose weight to deserve to be better, they were not well known for their "let's cut parts of the digestive system apart to help lose weight."<br />
<br />
There was another hospital, about an hour away from us, that did have a small bariatric surgery unit. They took me into the program, agreed to do the surgery if I lost X amount of weight first, and after reaching that milestone I had the surgery.<br />
<br />
Not long after, during the latter phases of physical recovery, I unceremoniously discovered that not only did my surgeon retire, but the hospital killed their bariatric surgery program. There was no notice. There was no letter, no email, no announcement ever reached us. Just...nothing. No more appointments kept.<br />
<br />
I soured on the medical system a little more at that point. There was emphasis on how important a support system was...and there is certainly no shortage of continued feeling that when a doctor looks at you, your weight is first a foremost on their mind when figuring out how much a person is worth.<br />
<br />
One day I had a consult about something at the local hospital and they mentioned the bariatric surgery, and how I could get followup at the other hospital.<br />
<br />
"We can't," I said. "They shut down their bariatric unit."<br />
<br />
"They restarted it a little while ago," they said.<br />
<br />
Turns out, with little (read: no) fanfare or notification, they revived their bariatric unit. I have no doubt the doctors I worked with are gone; my surgeon had retired, and I can't imagine the younger doctors stuck around once their specialty had been shut down.<br />
<br />
This came at a time when fat people were becoming (medically) profitable. Oh, sure, we're still a huge expense in cardiac care (and in this time the local hospital became a leader in cardiac care), but now some of those costs are being recouped through insurance companies through growing sleep apnea care, diabetes drugs and bariatric surgery. What was justification for treating people as sub-human was becoming a PR race to open the best fat-care centers, which before was the market for hucksters and easy diet schemers on television ads.<br />
<br />
In other words, upon hearing that the other hospital had re-opened their bariatric unit without any announcement to former patients, I figured it was because it was becoming fashionable and probably profitable to do so. I certainly didn't trust them to give a damn, though. They didn't notify their old patients about it. They expressed no damns about my status. So...screw them.<br />
<br />
The annoying thing is that the local hospital decided to focus more and more money into developing a local bariatric/weight loss program. As time went on they moved more staff into specializing on weight care. They repurposed a building just for weight loss. They focused resources on their weight loss center.<br />
<br />
But when the topic of weight loss came up with my appointments, the moment my surgery history came up it was suggested I drive another half hour to the other hospital and continue care there.<br />
<br />
It was during intake that I finally found out why. During the consult they mentioned something about checking the size of the stomach pouch, as it was obvious I could eat more than I was supposed to be able to. My history came up, and she said something about going to the other hospital.<br />
<br />
I recounted my history and my distaste for dealing with a hospital that made it so blatant they didn't give a damn about their patients. She said that she could talk to the surgeon in the local hospital's weight clinic, but she knew what he'd say...no, he wouldn't work with me on it. That was when I learned why.<br />
<br />
The government made rules.<br />
<br />
See, to make hospitals "accountable" (that's a big buzzword for hospitals now, not just schools!) they were getting evaluated based on patient followup. In this example, I was operated on by hospital A. They had a program they wanted to end, and they did...essentially dumping their patients.<br />
<br />
I ended up going to hospital B, my preferred hospital for most medical issues since I only went to A for a procedure B refused to do at the time. But this means that if anything was bariatric-related, B was getting (federally) evaluated for my poor outcome. At some point it seems A was pressured to re-open their bariatric program and make available their resources to old and new patients (although they didn't advertise it...take that as you will.)<br />
<br />
That was why I was repeatedly "encouraged" to go to another hospital for some weight treatment followups. It's also why I'm not able to access certain resources at a hospital that in the years following my surgery dumped not insignificant resources into developing a "cutting edge" bariatric unit.<br />
<br />
Once again the government is interfering in efforts they don't understand. Or at a minimum lots of hands in the pot have created a system that benefits not the patient, but some other interests, with the net effect of screwing the patient.<br />
<br />
In the end I still have to go through their weight clinic, just with some options limited. I get to begin the new year miserably tracking calorie counts and using words like "carbs" and "abs" and "veggies," and dealing with the neuroses that I know will flare up while pursuing the accurate tracking of goals.<br />
<br />
Will I be successful? Will I find more reason to distrust and/or outrightly dislike the hospital? Or will I fail miserably? Time will tell. But if you'll excuse me, I have to go prepare a big old egg patty with...egg. Lots of protein. Minimal carbs. Low calorie!<br />
<br />
I really miss food.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-11041453946088977172017-12-22T12:53:00.000-05:002017-12-22T12:53:08.386-05:00Golang Web Server: Don't Do ThisI still consider myself new to programming. The new job allows me to create a lot of small system tools using Go mostly for augmenting monitoring and create utilities to replace manual API calls using JQ and CURL with single executables created in Go. It's been a wonderful learning experience.<br />
<br />
Sometimes I try to add some new features to utilities that are snazzy but also a bit of an experiment.<br />
<br />
This is a bit of reflection on the design I originally used and I am not in a mood to pull out layers of source code to show what I had done, especially if no one is asking for it. But I will describe the basic design in an effort to not only avoid implementing it that way again but to warn others not to make the same design pattern mistake.<br />
<br />
The utility is mainly a long-running process that is interrogating one of our services for database information. It gets raw data from the database, pulls some stats like record size and type, and tallies the information. Millions and millions of records.<br />
<br />
What if, I thought, I provided a peek into what the state of the tallying is beyond what I already had showing? It would output a count of some basic information as a one-liner every thirty seconds to the console, but that wasn't good enough. I thought, why not create a web interface that would output a simple text page of information?<br />
<br />
Go loves channels. And I had several "worker goroutines" that handled specific tasks in the tally program, passing messages to a coordination process that serialized scheduling record analysis, directing results, and monitoring the state of various workers. Breaking them up made things pretty fast once I stuck in a few tweaks here and there.<br />
<br />
Adding a web server routine wasn't hard. Then I thought, I could just add a couple of channels to plug them into routines that held statistics.<br />
<br />
Here's where I made what later turned into a mistake.<br />
<br />
Instead of individual handlers, I created a single handler that took message strings via channels. The messages consisted of a random ID and a type, where the type was the page request.<br />
<br />
The reader on the other side of the channel split the message, used a select{} to determine which page it should construct, and returned through another channel the page with that ID string prepended. The receiver on the other side would look for the message and see if the ID belonged to its request. If it wasn't the proper ID, it just re-fed it to the channel, hoping that the right recipient would pick it up later, and the next message in the channel was intended for that particular reader. Line by line the page was fed back down the channel, with the ID attached to each message, until the ID was attached to a message: "END OF PAGE", at which point the page was done and connection closed.<br />
<br />
Don't do that.<br />
<br />
The thing is, this seemed to work. I opened a web browser, opened the page, and it worked. I could request the different pages and it worked just fine.<br />
<br />
It worked until one page got kind of big and I opened two web pages to the server. Something seemed to get "stuck." One of my statuses gave a snapshot of the fill state of some channels and I noticed some of the web-related channels were...throbbing? Growing huge and slipping down, as if revving up with more lines of messages than should possibly be needed. Something was getting misdirected and the lightweight speed of goroutines meant it was flooding channels with useless information.<br />
<br />
No problem, I thought. I'll add a third field, a counter, which once it reached a certain level would simply discard the message. The web page was meant to be read by a person who was trying to get some stats on the status of this utility while it was running, not the general public...refresh the page, hopefully you'll get a working reply that time. Sloppy, but might work.<br />
<br />
Tested again. It seemed to keep the channels from getting as clogged up, but I still had some kind of crosstalk that when pages grew larger, and it wasn't hard to create some kind of denial of service from the web server when two different pages were opened. It almost seemed as if sometimes the two pages got completely confused which tab was supposed to get what page.<br />
<br />
Maybe it was too easy to get messages mixed up because pages were feeding line by line. I went through the page composition and instead of feeding each line through, I had the process create one big string and feed the result.<br />
<br />
This cut down on responsiveness but increased reliability. Kind of. It was significant, but not enough to be proud of. If anyone tried pulling a web page from the utility while someone else used it there was a non-zero chance it would get a weirdly formatted page, if not a timeout.<br />
<br />
After finishing some work on other utilities, I decided to refactor the 4 web pages into their own handlers with separate functions and move some of the information being read into global structs with mutex's for protection. Before making the change I ran a test with <a href="https://github.com/codesenberg/bombardier" target="_blank">Bombardier</a>, a handing web server throughput tester. The test totally choked on the channel handler architecture.<br />
<br />
I refactored, separated out the page composition into individual handlers, and eliminated channels for web page feeding. No more IDs. No more parsing out replies. No more tracking how many times this particular message is making rounds before "expiring" it.<br />
<br />
Bombardier hammered away on the server with no issues. Multiple tabs reading different web pages? No problem. The biggest trigger for problems, clicking back or a link to one of the other pages while a large page hadn't finished rendering, was no longer a problem.<br />
<br />
What I wanted to do was find a way to read a URL request and use one handler to interpret what the client wanted, so I didn't need a number of individual handlers defined. I'm pretty sure I still could do that, but I think the weakness was in using channels with an associated ID to parse replies back to the client from a dedicated goroutine holding stats.<br />
<br />
The solution I ended up using was individual functions that read from a global struct holding the current state of statistics, and this was protected with a lot of locking.<br />
<br />
I suppose another way to do it, with channels, would be finding a way to spawn dedicated channels with each request so the replies didn't need parsing or redirecting; a channel with multiple readers has no guarantee of who is going to get the message at what point. This kind of fix seemed needlessly complicated, though.<br />
<br />
I suppose I could also have enhanced the global statistics struct to have functions associated with it, so calls could be made that would automatically lock and reply with information requested by callers. The utility is relatively small, though, and I thought that implementing that would have been more complicated than necessary. I'm not sure if this would enhance the speed of the program, though, and may be worth trying for the learning benefit.<br />
<br />
But what I definitely now know is not to pass web pages as composed lines with an ID tagged down a shared channel for a reader to parse and decided, "Is this line meant for me? No? Here, back into the channel you go, floating rubber ducky of information, while I read the next ducky...float away!"<br />
<br />
Don't do that.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-3660253152501472302017-11-26T22:45:00.001-05:002017-11-28T10:23:35.399-05:00StackOverflow and NewcomersStackoverflow (SO) is the premiere question and answer site for programmers. It's a joke now that when SO goes down, programmers go home because no work can get done. It is their mission to make life better for programmers, and the men and women working behind the scenes at SO have poured much sweat and tears into growing a useful community for programmers to share solutions to various problems encountered in their algorithm-laden lives. <br />
<br />
That is not to say there aren't issues, though. As the site has grown (and it is bit on the huge side now) SO has had to make decisions that define (and refine) the site's character, and not all of these desicions have passed without detractors. They have also had to try addressing criticism of the site, and one of the most common criticisms seems to be related to how (un)welcoming the site can be for newcomers. <br />
<br />
I think I can relate to this. I am not a programmer by trade, but I do try to create useful utilities for use in my day job and enjoy programming in at least a hobbyist capacity. I am not very confident in my abilities, though, and definitely do not need someone to remind me of an obvious skill gap (why do you think I'm asking the question in the first place?)<br />
<br />
I do not have the answers regarding how to make SO more welcoming to beginners. Perhaps once a community grows to a certain point it naturally fractures into a strata of people who are skilled to a point where they aren't aware of their own bias against lesser-experienced individuals. Or maybe there are rules in the system that encourage what one person interprets to be a "man up, you snowflake!" mentality while an insecure individual interprets the same feedback system to be validation that they don't have what it takes to join with programming peers. <br />
<br />
I suppose that when so much of the technology culture centers on a "Brogrammer" mentality rife with competition using knowledge and perceived cleverness as a ranking system, it's natural for some snark to become ingrained in interactions among programmer peers. It's not hard when reading some comments and answers to a SO question to sense a tone of judgement, that the questioner must pass some bar of having earned an answer before they may have one, something beyond the basic search of the site for the same problem before duplicating it.<br />
<br />
There have been cases where people will take more time to criticize the questioner than it would have taken to edit or refine the question into something useful and post an answer. <br />
<br />
Sometimes it seems you can do everything seemingly right but still fall short in someone's judgement; the ability to down vote a question while leaving no constructive feedback and incurring no penalty in the process (except to the question-asker) seems like a pretty obvious way to discourage interacting with the community for help.<br />
<br />
Note that I'm not saying down votes are necessarily bad, although I do wonder if alternative feedback methods could be useful. I'm saying that one of the more frustrating interactions on the site, in my experience, stems from being penalized and not knowing why; if you down vote, maybe you should have to leave some constructive feedback or enhancement to fix the problem or take some penalty to your own Internet-points reputation score. <br />
<br />
For example, I recently had trouble with an intermittent panic when exiting a Go utility and posted to StackOverflow for help. I posted a title that succinctly summarized the issue. I posted the panic message. I posted the function definition. The panic had a line number from the definition that seemed to trigger the intermittent error; I posted the specific "line X is..." followed by the line of code so there was no question what snippet triggered the panic. I tagged it with appropriate tags. There were a couple of comments, and I posted a link to another question citing some code to explain (justify?) why I implemented the function call the way I did. What happened?<br />
I took two down votes of penalty to my reputation. <br />
<br />
In the comments I asked if the down voters could explain what I could do to improve the question for future reference. After all, SO may be for answering questions related to your immediate problems, but it's also supposed to be of use to future questioners looking to solve similar problems. Last time I checked no one explained why they did it. <br />
<br />
The nearest I got to helpful feedback on the down votes was from one of the helpful people who submitted an answer to my question; that person speculated that it was because I had not RTFM'd to the satisfaction of some of the other users since the problematic line was in the panic and the source code for a function call used in my definition shows it probably didn't like a nil context parameter. <br />
<br />
So as a relatively insecure beginner, I crafted a question with lots of context, source code, and clarification, only to get dinged with damage (negative reputation) by anonymous clicks from people who couldn't leave a reason why or offer feedback on improving the reference value of the question. <br />
<br />
It shouldn't be difficult to understand why this would be discouraging to some people, especially when the goal (I thought) was to build a useful reference for many people, not (possibly) penalize someone for not meeting some arbitrary criteria for having passed a bar of RTFM to be blessed with community membership in order to be assisted without a passive aggressive backhand. <br />
<br />
I don't count myself as a detractor of StackOverflow. I have found help from members of their community to be invaluable. I do wonder if some of the feedback mechanisms sometimes encourages certain behaviors that deter less experienced and less thick-skinned programmers from interacting while enabling programmers with the "rock star" or "ninja brogrammer" mindset to set a less friendly tone. There comes a point where it's less commiserating and sharing with a community and more a necessary chore to solve a problem, and I suspect the gray area of that transition is where new users begin complaining about the tone of the site. <br />
<br />
<div id="blogsy_footer" style="clear: both; font-size: small; text-align: right;">
<a href="http://blogsyapp.com/" target="_blank"><img alt="Posted with Blogsy" height="20" src="https://blogsyapp.com/images/blogsy_footer_icon.png" style="margin-right: 5px; vertical-align: middle;" width="20" />Posted with Blogsy</a></div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-17071161867635510852017-11-03T20:44:00.000-04:002017-11-03T20:44:07.708-04:00Turning 40I turned 40 this week.<br />
<br />
Four decades. I remember there was a time I thought I'd grow up to "die alone as a hermit in the woods." I remember thinking maybe working as a programmer for Microsoft would be interesting. There was a time I thought I might become a marine biologist, specifically an ichthyologist, and study sharks. Later on I even flirted with the notion of working to become a successful author.<br />
<br />
Today I'm not working for Microsoft. I don't live in the woods, although the town I reside in is rapidly withering economically and some might argue our tiny dot on the map is not far removed from being woodland. I don't even own diving equipment and am nowhere near the ocean (although we do live on a river that ends in the ocean, if you want to travel a few hundred miles.) The closest I've come to becoming an author was finishing and editing exactly one manuscript.<br />
<br />
I'm pretty sure, at this point, that I have depression issues. I know it's more common today for people to talk about depression. For some people it is dismissed as an excuse of the week, or they brush it off as a "feeling blue" thing that you can exercise away or "just cheer up" to move past; "Just cheer up!" they say, totally ignoring that clinical depression is a thing.<br />
<br />
While this little shadow has always been lingering to some degree in the back of my mind, I've had some things really raise that shadow higher in prominence in the past few years. It would take chapters of a book to cover details, but the highlight reel would include attempts by my wife's employer to eliminate her from her job using what could be (in my view, as this is my opinion) charitably be labeled slanderous accusations. That was a year-long ordeal that took a huge emotional and financial toll on the family.<br />
<br />
After that drawn out mess, things finally felt like they were turning around. There was a light at the end of the tunnel! Unfortunately, it was a train's headlamp.<br />
<br />
The employer I had come to rely on for emotional and financial support decided to terminate my contract, which is a nice way of saying I was sent home with a box of my belongings. Now it was my turn to plunge into a world of uncertainty, doubt, and the five stages of grief. I was blindsided and even the act of getting out of bed felt like fighting a dark shroud squeezing the life out of me.<br />
<br />
Worse yet, if you feel like taking a moral stance and voicing support for teachers in the never ending fight over contracts, even if your family has been working in public education for decades, even if you do this by pointing out actual evidence straight from the faces of the people you feel are in the wrong, you might want to think twice if this takes place in a town that is turning into the economic equivalent of a mummy and you might have to return and look for a job. I made some statements that gained some traction among certain circles here; at the time I felt secure in the idea that my employment was secure in the land of gummy bears and unicorns. The reversal of fortune played right into the hands of depression's self doubt and uncertainty, whispering that "they" are laughing at my incompetence as I searched for job openings in a town propped up by Wal-Mart, McDonalds, a hospital system and the public education system whose administration and board are not pleased with you for writing something that was popular for a couple days among their staff.<br />
<br />
I also experienced firsthand the silence from most of the people I had taken for granted as friends and associates from what I eventually came to regard as my "previous life."<br />
<br />
These were two major events. I was already dealing with issues and stresses that many others have to deal with in life. These two major events just fanned the depression flames.<br />
<br />
Now we have a national problem; we became a Trumpster fire nation. Every day came a new display of ignorance and people taking pride in how terrible they can be. I don't feel that there's much to act as a counterbalance against the papercuts of negativity he and his followers display.<br />
<br />
It's been a long, stressful, painful period of time.<br />
<br />
It's also been nearly a year since I started my new job, which gave me some sense of self worth again. Slowly it helped build up some sense of validation that I'm not worthless. I'm not sure if that makes sense or if I'm laying another misplaced sense of power into the hands of something in which I shouldn't emotionally invest. But for now it's there and helping me.<br />
<br />
My family has been supportive during this emotional roller coaster, or tried to be. I don't think I quite acknowledge the good they do as much as I focus on negative things that families deal with. That's a side effect of both depression and Aspergian brain wiring, I think. Given the reflection hitting four decades of sentience has triggered, I think I need to continue trying to improve on that behavior.<br />
<br />
All of these things have combined into a hazy mire that congealed into a cloud around me, affecting my worldview and keeping me in a perpetual weariness. I thought my birthday, despite being a magic number (I love the number 4, and 10 is a binary number as well as the number of digits on my hands and the number of digits on my feet, and is even, and possesses several other attributes that lend an irrational appreciation in my mind), would be yet another quiet passage marked by some cards and well wishes and soon forgotten. It was even on a Wednesday, my least favorite day for events to occur.<br />
<br />
Usually the big booster in looking forward to my birthday is that it is preceded by Halloween. I love the idea of Halloween; the image of trick or treat, costume parties, awesome DIY costumes, parades, and horror movies are so much fun for me. But this year was different; the Friday before my birthday brought an announcement that indictments were coming against Trumpster acquaintances! After an anticipation-filled weekend, Monday had people brought in to testify, and we discovered one of his campaign associates had already pled guilty to lying to police and was cooperating with investigators!<br />
<br />
We went out for dinner on my birthday with my in-laws and parents. One of the TV's played MSNBC's coverage of Trump's Russian connections and the mounting investigations. I was giddy.<br />
<br />
My birthday was also marked by the Daily Show having an interview with Hillary Clinton. I don't know why that made me happy...I guess because she's the symbol of everything "I told you so" during the Presidential election.<br />
<br />
These were things that worked to fight the shroud of depression whispering in my ear, and were totally counter to the idea that my 40th birthday would be quiet. These were things that were happy events for me.<br />
<br />
There were other, not so happy events that marked the birthday-time. Unexpected shocks like the guy who rented a truck and ran over bike riders in downtown Manhattan. Because he wasn't white, it was labeled as an act of terrorism, unlike the recent Vegas shooting of around 600 people by a white guy where the fallout is basically several people going bankrupt from medical bills and modifications the shooter made to his guns staying perfectly legal and Congress clutching pearls at the idea that nothing can prevent these things from happening.<br />
<br />
Yet another shocking event involved layoffs at a previous employer. I discovered it as oddly worded and vague tweets began floating along my Twitter timeline; today there was a <a href="https://techcrunch.com/2017/11/02/stack-overflow-lays-off-staff/" target="_blank">Techcrunch article</a> giving conflicting details of what had happened. In the end I could only confirm that a relatively large number of people were let go, some of whom I knew and had worked with so it wasn't just trimming the newest of hires. In keeping with the "Me me me!" theme, this news caused me to revisit all the thoughts of despair and hopelessness that I felt as my wife drove me home from the apartment after I was told my time there had ended. I empathized with what must be a swirl of confusion and fear that these people now feel. I also watched as people who escaped the cutting block echoed their support for one another and words of sadness to their departed colleagues. Selfishly I felt like the bandage was ripped off an old wound.<br />
<br />
I turned 40 this week.<br />
<br />
Nothing I thought was going to happen as a teen happened. Getting older shifted into a pattern where almost every day blended into the next; mostly unremarkable, smeared with a veneer of depression and frustration, life is mostly a comfortable pattern of routine. I expected it to be yet another average day, but this birthday was marked with some surprises. Some good. Some bad. But one thing this birthday wasn't is uneventful.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-84501058074053854322017-10-18T18:06:00.001-04:002017-10-18T18:06:20.256-04:00Reflection on CodingThere's a subject I've been thinking about lately. I suppose it's more of a feeling than a topic; I'm not even sure how to put it into words.<br />
<br />
I have a vague feeling that I've discussed it before, too. In some form. On the other hand, maybe writing about it will help get it out of my head.<br />
<br />
The best I've managed to do to express this feeling is to frame it as "elegant beauty," or a kind of beauty that comes from expression through the logic of programming.<br />
<br />
It's not that this is an entirely new concept. I've often read descriptions of Ruby as poetic, and there are other works that try examining questions like whether programming is more art than science, or <a href="https://medium.com/words-escape-us/is-programming-poetry-aa137a2ac3ca" target="_blank">whether programming is poetry</a>.<br />
<br />
Perhaps part of this is my own brains weird wiring. I sometimes have trouble understanding poetry; good poetry can "work" on so many levels. Clever word use, double entendre, use of linguistic beats to emphasize points, references to other events and works, parallels to other art forms...I'm sure my wife, an English major, is able to expound on (and expand) the topic far more than I.<br />
<br />
Programming adds yet another dimension: it is functional. It takes a language, with its own unique grammar and syntax, and processes input into something else. It's an expression of formulas through rules. If you get the syntax wrong, your work won't compile into a finished product. Programming is notoriously unforgiving when straying from the language rules.<br />
<br />
And yet programs that take a set of input and produce the same output can still have so much variety!<br />
<br />
I suppose a simple example can use the infamous FizzBuzz program. It's a staple of many a coding interview; relatively simple, it has, over time, become almost cliche (and in some circles, despised, depending on the blogs you read and the type of programmer bemoaning how demeaning it is to be asked to demonstrate it...)<br />
<br />
The rules are simple; usually some variant of, "Count from 1 to 100, and if a number is divisible by 3, print "Fizz." If it is divisible by 5, print "Buzz". If it is divisible by 3 and 5, print "FizzBuzz." Otherwise, print the number.<br />
<br />
The simplest and most crude way to program this is to literally lay out a program that counts from 0 to 100 and use <b>if</b> statements to output Fizz, Buzz, and FizzBuzz in the appropriate places. It would achieve the goal of the rules, but be highly inefficient and inflexible.<br />
<br />
The next step up might be something like this:<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// FizzBuzz</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"strconv"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a loop to count 1 to 100</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a string variable that gets reinitialized each iteration</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">strOutput</span> <span style="color: #cdcaa9; font-weight: bold;">string</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: #0086d2;">""</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Fizz on 3</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">i%</span><span style="color: #0086f7; font-weight: bold;">3</span> <span style="color: white;">==</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: #0086d2;">"Fizz"</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Buzz on 5</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">i%</span><span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">==</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: #0086d2;">"Buzz"</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Otherwise, output the number</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">strOutput</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strconv.Itoa(i)</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Print the result</span>
<span style="color: white;">fmt.Println(strOutput)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
If you know modulo, FizzBuzz is a pretty straightforward logic problem. But what if you didn't know about that piece of math?<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// fizzbuzz-simple.go</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"strconv"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">a</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">a</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">;</span> <span style="color: white;">a++</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">strOutput</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">=</span> <span style="color: #0086d2;">""</span>
<span style="color: white;">intTmp</span> <span style="color: white;">:=</span> <span style="color: white;">a</span> <span style="color: white;">/</span> <span style="color: #0086f7; font-weight: bold;">3</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">intTmp*</span><span style="color: #0086f7; font-weight: bold;">3</span> <span style="color: white;">==</span> <span style="color: white;">a</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: #0086d2;">"Fizz"</span>
<span style="color: white;">}</span>
<span style="color: white;">intTmp</span> <span style="color: white;">=</span> <span style="color: white;">a</span> <span style="color: white;">/</span> <span style="color: #0086f7; font-weight: bold;">5</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">intTmp*</span><span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">==</span> <span style="color: white;">a</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: #0086d2;">"Buzz"</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">strOutput</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strconv.Itoa(a)</span>
<span style="color: white;">}</span>
<span style="color: white;">fmt.Println(strOutput)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
This is probably a little slower...to be honest, I'm not sure if the compiler would optimize this into similar binary algorithms. But the end result is still the same.<br />
<br />
The first issue I'd have with the basic implementation is that it's not very modular. It might be better to use a function to determine the fizzing and the buzzing.<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// fizzbuzz-func.go</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"strconv"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a loop to count 1 to 100</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Fizz on 3</span>
<span style="color: white;">strOutput</span> <span style="color: white;">:=</span> <span style="color: white;">CheckMod(i,</span> <span style="color: #0086f7; font-weight: bold;">3</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Fizz"</span><span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Buzz on 5</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: white;">CheckMod(i,</span> <span style="color: #0086f7; font-weight: bold;">5</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Buzz"</span><span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Otherwise, output the number</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">strOutput</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strconv.Itoa(i)</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Print the result</span>
<span style="color: white;">fmt.Println(strOutput)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">CheckMod(intCount</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">intCheck</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">strLabel</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">intCount%intCheck</span> <span style="color: white;">==</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">strLabel</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #0086d2;">""</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
This version includes a simple CheckMod() function that can be called to see if the remainder when divided by a supplied integer should get a label; now it takes minimal editing to change the numbers for which Fizz, Buzz, or FizzBuzz are used as output!<br />
<br />
And, of course, this still has the same output as the previous versions.<br />
<br />
But what if we don't want to keep modifying the source code to alter the Fizz and Buzz triggers? That's simple too.<br />
<br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// fizzbuzz-func-flags.go</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"flag"</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"strconv"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="color: white;">intCountTo</span> <span style="color: white;">:=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"countto"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Count from 1 to this number"</span><span style="color: white;">)</span>
<span style="color: white;">intFirstNum</span> <span style="color: white;">:=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"firstnum"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">3</span><span style="color: white;">,</span> <span style="color: #0086d2;">"First number to label"</span><span style="color: white;">)</span>
<span style="color: white;">strFirstLabel</span> <span style="color: white;">:=</span> <span style="color: white;">flag.String(</span><span style="color: #0086d2;">"firstlabel"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Fizz"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"First label to substitute"</span><span style="color: white;">)</span>
<span style="color: white;">intSecondNum</span> <span style="color: white;">:=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"secondnum"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">5</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Second number to label"</span><span style="color: white;">)</span>
<span style="color: white;">strSecondLabel</span> <span style="color: white;">:=</span> <span style="color: white;">flag.String(</span><span style="color: #0086d2;">"secondlabel"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Buzz"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Second label to substitute"</span><span style="color: white;">)</span>
<span style="color: white;">flag.Parse()</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a loop to count 1 to x</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><=</span> <span style="color: white;">*intCountTo;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Fizz on y</span>
<span style="color: white;">strOutput</span> <span style="color: white;">:=</span> <span style="color: white;">CheckMod(i,</span> <span style="color: white;">*intFirstNum,</span> <span style="color: white;">*strFirstLabel)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Buzz on z</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: white;">CheckMod(i,</span> <span style="color: white;">*intSecondNum,</span> <span style="color: white;">*strSecondLabel)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Otherwise, output the number</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">strOutput</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strconv.Itoa(i)</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Print the result</span>
<span style="color: white;">fmt.Println(strOutput)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">CheckMod(intCount</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">intCheck</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">strLabel</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">intCount%intCheck</span> <span style="color: white;">==</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">strLabel</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #0086d2;">""</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
Now there are command line flags that designate the Fizz and the Buzz (as well as possible new labels for Fizz and Buzz) and the number to count to!<br />
<br />
Because there are defaults added in to the flag variables, the default version of this...with no flags set at the command line...will have identical output to the previous applications.<br />
<br />
This version added quite a bit of flexibility to the program, and that flexibility is accessible from the command line by the end user. There is another problem, though; if you intend for an end user to use this application, there should be some sanity checking for the things they can change.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// fizzbuzz-func-flags-errcheck.go</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"flag"</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"os"</span>
<span style="color: #0086d2;">"strconv"</span>
<span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// A struct of flags</span>
<span style="color: #fb660a; font-weight: bold;">type</span> <span style="color: white;">stctFlags</span> <span style="color: #fb660a; font-weight: bold;">struct</span> <span style="color: white;">{</span>
<span style="color: white;">intCountTo</span> <span style="color: white;">*</span><span style="color: #cdcaa9; font-weight: bold;">int</span>
<span style="color: white;">intFirstNum</span> <span style="color: white;">*</span><span style="color: #cdcaa9; font-weight: bold;">int</span>
<span style="color: white;">strFirstLabel</span> <span style="color: white;">*</span><span style="color: #cdcaa9; font-weight: bold;">string</span>
<span style="color: white;">intSecondNum</span> <span style="color: white;">*</span><span style="color: #cdcaa9; font-weight: bold;">int</span>
<span style="color: white;">strSecondLabel</span> <span style="color: white;">*</span><span style="color: #cdcaa9; font-weight: bold;">string</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">strctFlags</span> <span style="color: white;">stctFlags</span>
<span style="color: white;">strctFlags.intCountTo</span> <span style="color: white;">=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"countto"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Count from 1 to this number"</span><span style="color: white;">)</span>
<span style="color: white;">strctFlags.intFirstNum</span> <span style="color: white;">=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"firstnum"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">3</span><span style="color: white;">,</span> <span style="color: #0086d2;">"First number to label"</span><span style="color: white;">)</span>
<span style="color: white;">strctFlags.strFirstLabel</span> <span style="color: white;">=</span> <span style="color: white;">flag.String(</span><span style="color: #0086d2;">"firstlabel"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Fizz"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"First label to substitute"</span><span style="color: white;">)</span>
<span style="color: white;">strctFlags.intSecondNum</span> <span style="color: white;">=</span> <span style="color: white;">flag.Int(</span><span style="color: #0086d2;">"secondnum"</span><span style="color: white;">,</span> <span style="color: #0086f7; font-weight: bold;">5</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Second number to label"</span><span style="color: white;">)</span>
<span style="color: white;">strctFlags.strSecondLabel</span> <span style="color: white;">=</span> <span style="color: white;">flag.String(</span><span style="color: #0086d2;">"secondlabel"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Buzz"</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Second label to substitute"</span><span style="color: white;">)</span>
<span style="color: white;">flag.Parse()</span>
<span style="color: white;">EvalFlags(&strctFlags)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a loop to count 1 to 100</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><=</span> <span style="color: white;">*strctFlags.intCountTo;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Fizz on 3</span>
<span style="color: white;">strOutput</span> <span style="color: white;">:=</span> <span style="color: white;">CheckMod(i,</span> <span style="color: white;">*strctFlags.intFirstNum,</span> <span style="color: white;">*strctFlags.strFirstLabel)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Buzz on 5</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strOutput</span> <span style="color: white;">+</span> <span style="color: white;">CheckMod(i,</span> <span style="color: white;">*strctFlags.intSecondNum,</span> <span style="color: white;">*strctFlags.strSecondLabel)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Otherwise, output the number</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">strOutput</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">strOutput</span> <span style="color: white;">=</span> <span style="color: white;">strconv.Itoa(i)</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Print the result</span>
<span style="color: white;">fmt.Println(strOutput)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">EvalFlags(strctFlags</span> <span style="color: white;">*stctFlags)</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">*strctFlags.intCountTo</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"-countto must be greater than 0"</span><span style="color: white;">)</span>
<span style="color: white;">os.Exit(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">*strctFlags.intFirstNum</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"-firstnum must be greater than 0"</span><span style="color: white;">)</span>
<span style="color: white;">os.Exit(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">*strctFlags.strFirstLabel</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"-firstlabel must have a text label"</span><span style="color: white;">)</span>
<span style="color: white;">os.Exit(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">*strctFlags.intSecondNum</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"-secondnum must be greater than 0"</span><span style="color: white;">)</span>
<span style="color: white;">os.Exit(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">*strctFlags.strSecondLabel</span> <span style="color: white;">==</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"-secondlabel must have a text label"</span><span style="color: white;">)</span>
<span style="color: white;">os.Exit(</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Done</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">CheckMod(intCount</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">intCheck</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">strLabel</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">intCount%intCheck</span> <span style="color: white;">==</span> <span style="color: #0086f7; font-weight: bold;">0</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">strLabel</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: #0086d2;">""</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
Now the application checks for things like labels being set to some kind of string and not an empty string, and all the numbers are set to something greater than 0. Basic error checking.<br />
<br />
And once again...the output, by default, will match the output of the previous programs!<br />
<br />
These are all rather straightforward. It doesn't really take advantage of features specific to Go, like channels (<a href="https://play.golang.org/p/ssVH6Rg3-Z" target="_blank">Here is the link to the Go playground implementation from Russ Cox</a>, reproduced here:)<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: #0086d2;">"fmt"</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="color: white;">c</span> <span style="color: white;">:=</span> <span style="color: white;">generate()</span>
<span style="color: white;">c</span> <span style="color: white;">=</span> <span style="color: white;">filter(c,</span> <span style="color: #0086f7; font-weight: bold;">3</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Fizz"</span><span style="color: white;">)</span>
<span style="color: white;">c</span> <span style="color: white;">=</span> <span style="color: white;">filter(c,</span> <span style="color: #0086f7; font-weight: bold;">5</span><span style="color: white;">,</span> <span style="color: #0086d2;">"Buzz"</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">s</span> <span style="color: white;">:=</span> <span style="color: white;"><-c;</span> <span style="color: white;">s</span> <span style="color: white;">!=</span> <span style="color: #0086d2;">""</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(s)</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(i)</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">generate()</span> <span style="color: white;"><-</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">{</span>
<span style="color: white;">c</span> <span style="color: white;">:=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: white;">c</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">""</span>
<span style="color: white;">}</span>
<span style="color: white;">}()</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">c</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">filter(c</span> <span style="color: white;"><-</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">,</span> <span style="color: white;">n</span> <span style="color: #cdcaa9; font-weight: bold;">int</span><span style="color: white;">,</span> <span style="color: white;">label</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span> <span style="color: white;"><-</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span> <span style="color: white;">{</span>
<span style="color: white;">out</span> <span style="color: white;">:=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">i</span> <span style="color: white;">:=</span> <span style="color: #0086f7; font-weight: bold;">0</span><span style="color: white;">;</span> <span style="color: white;">i</span> <span style="color: white;"><</span> <span style="color: white;">n-</span><span style="color: #0086f7; font-weight: bold;">1</span><span style="color: white;">;</span> <span style="color: white;">i++</span> <span style="color: white;">{</span>
<span style="color: white;">out</span> <span style="color: white;"><-</span> <span style="color: white;"><-c</span>
<span style="color: white;">}</span>
<span style="color: white;">out</span> <span style="color: white;"><-</span> <span style="color: white;"><-c</span> <span style="color: white;">+</span> <span style="color: white;">label</span>
<span style="color: white;">}</span>
<span style="color: white;">}()</span>
<span style="color: #fb660a; font-weight: bold;">return</span> <span style="color: white;">out</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
I should note that I created a blog past blog post that explored the channels implementation above...<br />
<br />
The simple Fizz Buzz test in the forms above have the same output, but it's accomplished in many ways. I'm sure there are people who would be able to send variations that also have the same end result using a different algorithmic logic; logical, and possessing a strict set of rules that must conform to the expectations of the compiler, but still arriving to the same destination through different means.<br />
<br />
To understand the source code means twisting your brain into understanding how the programmer responsible for the source code thinks and expresses his or her way of thinking against those rules of the programming language's grammar and syntax.<br />
<br />
The examples above are a peek into some of the evolution in my own thinking about how to program a task, how my own thinking in Go had gradually focused on aspects to increase maintainability and flexibility while accomplishing a goal. I wonder if this is the kind of evolution that is looked for by interviewers for programming jobs...although that's a dangerous thought, considering that the expectation for defining the rungs of skill on that ladder of skill could be dangerously arbitrary.<br />
<br />
I'm still refining my methods of modeling tasks when programming. I'm changing workflows, how I comment, and what I comment. I still occasionally reel back, perplexed, when seeing some samples of other people's code and have no idea why...or how...they thought the problem through the way they did.<br />
<br />
Each sample I write or read is a reflection of the person who wrote it.<br />
<br />
Sometimes I wonder what my own reflects about me.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-27990629538335122662017-09-24T21:31:00.000-04:002017-09-24T21:31:03.792-04:00One Example of How To Tell When Obligations are Wastes of Time(Disclaimer: everything here is my own opinion. I ordinarily shouldn't have to mention this, but there are times where mentioning certain catalysts for thoughts tends to make those catalysts angry and definitely not do things that resemble acts of retaliation against people who aren't me but are related in some way to me. This isn't even about them. But I feel I have to explicitly state that because sometimes the catalysts may not be very good at reading comprehension.)<br />
<br />
The local paper recently had an article announcing that a local school superintendent was awarded the highest rating for his performance. I personally wasn't too surprised given that during negotiations over teacher contracts, questions for his opinion on the matter were something to the effect of, "I serve at the will of the board."<br />
<br />
But I wouldn't speak ill of the school board. When criticized, things happen that are <i>definitely not retaliation </i>against people who are related to me in their district. And this isn't about the board. It's about the obligations that make boards...or any regulated body...look like they're doing work when really it's a waste of time and opportunity to rubber stamp their own work (or use it as an excuse to get rid of someone that displeases the regulated body in some way).<br />
<br />
The news report had said that there were four performance ranks that could be given: distinguished, proficient, needs improvement or unsatisfactory. Having a set of scores aggregated in sets of one to four isn't necessarily bad...even Netflix now has a rating system based on a 1 or 2, which of course eliminates all the nuance of "The movie didn't make me want to throw up, but I definitely wouldn't want to watch it again" and instead reduces the viewing experience to "I LOVED THIS FILM" or "This film is so terrible that it will become a niche cult classic in 10 years when the latest group of self-appointed film buffs rediscovers it and nit picks the flaws into virtues."<br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">What areas were evaluated? "Professionalism, human resource management, district operations and financial management, student growth and achievement, organizational leadership, and communication and community relations."</span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">A key question to ask is, how are these evaluated? These are standards. It's spelled out in school laws established in "<a href="http://www.legis.state.pa.us/cfdocs/legis/LI/uconsCheck.cfm?txtType=HTM&yr=1949&sessInd=0&smthLwInd=0&act=14&chpt=10&sctn=73&subsctn=1" target="_blank">1949 Act 14</a>": "...the employment contract for a district superintendent or assistant district superintendent shall include objective performance standards..."</span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">To figure out if this is actually useful or a waste of time when referring to an obligatory standard, you need to ask yourself against what ruler the standards are measured, and ask how the areas being measured are established.</span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">The article said nothing about the scores other than the board members sat down and filled in sheets that were aggregated and found to be wonderful. Some objectives seemed like they'd be easy to measure, such as "student growth and achievement," something that has plenty of semi-effective rules regulating measures of student test results. Other things are blatantly arbitrary. How do you measure professionalism? You get a minus one each time you show up wearing a clown outfit? Or do you get a minus one for not wearing a tie, a minus two for wearing a "fun" tie, and a minus five for dressing as Pennywise? </span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
Not having standards that can be objectively measured is a strong indication that you're dealing with a feel-good waste of time.<br />
<br />
What about what or who establishes the items to be measured? This time around the superintendent had to post a list of what was to be measured on the school website. After some digging around, I found the list. Apparently the list is determined by the person being evaluated, then the board okays it (which again is allowed by the school code...it turns out the "standards" for evaluation are "mutually agreed to").<br />
<br />
I won't comment on how weird it is that the first half of the <a href="http://www.athensasd.k12.pa.us/wp-content/uploads/2017/09/Superintendent-Goals-2017-2018.pdf" target="_blank">letter to the board</a> is a word for word match to <a href="http://www.bpsd.org/AnnualSuperintendentPerformanceStandards.aspx" target="_blank">another district's older set of "standards"</a> (although it does make me wonder if those items are actually, as it states, "set forth in the Superintendent's Contract are as follows:"...)<br />
<br />
Instead I'll point out statements such as, under "School District Operations and Financial Management", that the "<i>Superintendent shall manage effectively, ensuring completion of activities associated with the annual budget, oversee distribution of resources in support of School District priorities, and direct overall operational activities within the School District.</i>"<br />
<br />
What does that even mean? Manage effectively meaning, this job is completed? And what is the job? Ensuring the completion of activities related to the budget would basically mean you check in on the person or people in charge of actually creating the budget. Oversee resources being allocated to District priorities means what, if not making sure money goes into proper budgets and books go to the right classes? And directing overall operational activities means he's in charge of the district which, oddly enough, is what a superintendent DOES.<br />
<br />
This whole paragraph sounds like he's being evaluated on whether he actually does his job. And I also noticed there's no actual gauge by which to measure it. The measure is arbitrary.<br />
<br />
There's a section called Organizational Leadership, under which it states, "<i>Superintendent shall work collaboratively with the Board to develop a vision for the School District, display an ability to identify and rectify problems affecting the School District, work collaboratively with School District administration to ensure best practices for instruction, supervision, curriculum development, and management are being utilized, and work to influence the climate and culture of the School District.</i>"<br />
<br />
What does that mean? The superintendent will work with the board to establish a vision for the district, which under ordinary conditions would make sense, except when he clearly said during contract negotiations that he serves at the will of the board. The translation would therefore imply that either the board is coming up with the vision, or he's going to propose something that the board will vote to pass if they don't want to come up with one.<br />
<br />
And "display an ability to identify and rectify problems affecting the School District"? I'd be interested in hearing someone talk about a time when a superintendent talks about the problems of their district. I don't recall hearing something like that from the superintendents of our local districts.<br />
<br />
The last part is also vague--influence the climate and culture of the district? I'm not sure there is an objective measure for cultural influence. Most "culture and climate" I've heard regarding the school comes from the community, and much of that is influenced by the public and opinions spread by the school board during contract negotiations...and it's rarely positive. The statement itself doesn't even say he's going to positively or negatively influence the climate and culture. As the "head" of the district serving at the pleasure of the board, he could achieve this objective just by establishing a baseline expectation that when an issue is brought to his attention, the staff knows what they'll expect will probably happen, for better or worse.<br />
<br />
And again, this has no objective measure against which to base a standard to score.<br />
<br />
That brings me to the next sign you're dealing with a waste of time. The language is flowery, but vague. Stopping to translate paragraphs into actual meaning shows they aren't really meaning much at all once boiled down.<br />
<br />
The last part of the letter is supposed to spell out how he is going to meet his objectives. It has items like, "Increase interventions and remediation's (sic) for students who need it most before, during, and after school", and, "Create a long range and comprehensive strategic plan - WILDCAT 2025".<br />
<br />
If you thought I'd call this a waste of time, you'd be wrong. The list reads like a checklist, and having a checklist isn't a bad thing. If your goal is to get these things accomplished in the course of the upcoming year, that's great.<br />
<br />
If anything were wrong with it, it's that this is a checklist in the context of a subjective set of standards by which to measure the performance of the person in charge of the district. If you judge a sports player and his or her checklist includes an item to improve the distance he or she throws the ball, that's great. But how much? 10% farther? 10 feet farther? Does he or she get points based on how many feet they improve the throw, or the quality of the throw by combining the distance with accuracy?<br />
<br />
So why go through the effort of publishing a story about a superintendent being rated insanely great by the board that hired him in the first place and spent the past year "serving at the will of the board?" It's entirely a matter of speculation, and I can't engage in speculation because that could lead to definitely not retaliation. And this evaluation is just one more example of something mandated by the state that probably started with good intentions and mutated into a pathetic waste of time as it bounced around various fingers before becoming part of the law. But it's important to be able to apply critical thinking and differentiate when something reaching the public is worthwhile news and when something is little more than a waste of time.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-30719414252856170172017-09-04T16:27:00.000-04:002017-09-04T16:27:22.177-04:00Formatting WoesAm I the only one that uses Blogger and keeps discovering formatting issues?<br />
<br />
I take time to review my posts. I preview them. I lay out the fonts and paragraphs to include spacing that breaks up the sections for increased readability. I wrap text around graphics and use captions for text specific to that image.<br />
<br />
It seems like no matter how much time I put into carefully laying out the format of the page, at some point I view the post as a regular user and...WHY IS THE SPACING GOOFED UP?<br />
<br />
Is it Blogger? Is it a side effect of the template used for the layout? Certain fonts used?<br />
<br />
I don't know.<br />
<br />
I just know that it's incredibly frustrating.<br />
<br />
It seems odd that even when I preview a post, adjust spacing, and finally post, the end result is still..."off".<br />
<br />
I have several subjects to write about. Periodically noticing screwed up posts led me to write this up first.<br />
<br />
As I type this I'm still using blogger...but I'm tempted to try another platform. Maybe someday I'll shift everything to another site and if I do, it will simply not make sense because formatting actually looks sane.<br />
<br />
On the other hand, moving to a sane site...different template?...may cause the adjustments I tried using to "fix" errors to actually make things weird in some other way.<br />
<br />
Maybe time will tell.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-32860588743202895902017-08-01T19:04:00.000-04:002017-08-31T11:06:06.801-04:00More Tuning Golang Apps for High Concurrency Tasks on LinuxI have a project that is fairly straightforward. Again it's work related, so I have to fuzz some details, and again my memory is naturally fuzzy so I doubt it's an issue.<br />
<br />
<h2>
<b>Background</b></h2>
<div>
<b><br /></b></div>
This program I've been working on makes calls to a service (a REST endpoint) that in turn pulls data from a database, then my application parses that information into components and checks the disk to see if the file already exists. If it doesn't exist on disk already, the program makes a call to the API endpoint again asking for specific record information and writes it to the disk. In the end I get a huge set of files sorted in a structure resembling ./files/year/month/day/datatype/subtype/filename.txt.<br />
<br />
There are literally millions of records to sort through. A single thread handling this would probably take weeks. Therefore, the program uses several (configurable!) goroutines to pull records simultaneously.<br />
<br />
<h2>
First Problem: Too Many Open Files</h2>
<div>
<br /></div>
I wrote about this fix earlier in the blog, but I'll give a quick recap.<br />
<br />
At first everything seemed fine. I have a simple bit of math being output periodically to the console, and it was chugging along at around 20,000 records/minute. The system was functioning fine, no errors were showing up. All was right with the world.<br />
<br />
Then a few hours later a few alerts arrived in the email. At this point the utility was running on its own instance with its own storage and was making calls to a load balancer that held a few endpoint servers behind it. The only change to the system that could possibly prevent the system from making connections was the utility I was running, so I killed it, and when the API servers were checked there were still 18,000+ network connections in TIME_WAIT on each system.<br />
<br />
Linux systems treat files on the disk as well as sockets as "open files" due to the way Linux handles file handles. Too Many Open Files can mean literally too many files are open or it can mean too many network connections are open, but it usually is a combination of the two.<br />
<br />
Research time. The problem here is usually related to "you didn't close the connections." That wasn't the cause here. The calls were straightforward; I had a function that created a transport, created a client, made the connection, and called GET then read the data to return to the caller. It was a textbook example fragment of Go adapted to my purposes, and that included a defer Close() call so when the function exited it should make really sure everything was closed properly.<br />
<br />
Check the "did you close the connection" off the list. And I also read the data from the socket before closing it, so that can be checked off the list. I had a hacked together bit of logic to retry connections if there was an error, but it also printed that status to the console when that happened. Nothing appeared as the too many open files errors popped up, so even if that caused a socket leak, it wasn't the likely cause.<br />
<br />
The issue was the call to instantiate a transport each time the function was called. Transports hold the pool of client connections; the system should be re-using connections. Because the transport was destroyed each time the function returned, it was creating a new pool of connections, which meant new sets of client connections to the server instead of recycling previous connections and that led to thousands of "open files".<br />
<br />
The solution was to create the transport and pass it as a parameter to the call to GET the web endpoint. This allowed the transport to continue to manage the client pool outside the scope of the function call, and that allowed the system to keep a managed pool of connections for re-use.<br />
<br />
This wouldn't have shown up if I were making periodic, occasional calls to different websites every few minutes. The problem would still be there, but chances are the connections would eventually close and time out before piling up and becoming a problem.<br />
<br />
<h2>
Too Many Files Leads to Terrible Times</h2>
<div>
<br />
<br /></div>
There are a few things that are obvious in affecting the speed information is being processed, and at the risk of sounding immodest, I've been told I'm pretty good at spotting the obvious.<br />
<br />
Warning: I'm not a Go expert. I'm citing information here that is just my current understanding, so if I'm wrong, please correct me in the comments.<br />
<br />
Because I'm writing files, the drive can definitely affect performance. I have multiple processes that could be trying multiple disk operations in parallel at a given time. To that end, disk seek times, write times, and cache can directly impact the utility's speed.<br />
<br />
I'm dealing with millions of files. During the initial testing and design of the utility, I had to deal with a file that would unzip into a directory holding around 100,000 files; then I had to deal with several of those 100K-file containing directories for processing. If you haven't tried that on a Macintosh using the HFS+ filesystem, it's not fun. EXT4 doesn't really handle it well either. Even on an SSD, getting a directory listing is downright painful. Too many files in one directory is difficult for some filesystems to handle.<br />
<br />
One solution is to split the directory into more subdirectories, reducing the number of entries the system has to track per directory. This is in fact the solution I used, splitting information into logical subsets.<br />
<br />
<h2>
Timing Out Connections</h2>
<div>
<br /></div>
Another fun fact I learned during this project; by default, the Golang client doesn't have timeouts set. This leads to some fun havoc with stray connections left in weird states that if you're using them to hit random sites in a semi-random fashion, you'd probably never notice. Hammer the same site with hundreds of requests per second, and you can bet this can have some ramifications.<br />
<br />
I read about this in a <a href="https://medium.com/@nate510/don-t-use-go-s-default-http-client-4804cb19f779" target="_blank">blog post warning against using the default settings in http.Client</a>. After reviewing that information, I went back to the source code and added some timeouts, like so:<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"> <span style="color: white;">tr</span> <span style="color: white;">:=</span> <span style="color: white;">&http.Transport{</span>
<span style="color: white;">Dial:</span> <span style="color: white;">(&net.Dialer{</span>
<span style="color: white;">Timeout:</span> <span style="color: #0086f7; font-weight: bold;">30</span> <span style="color: white;">*</span> <span style="color: white;">time.Second,</span>
<span style="color: white;">}).Dial,</span>
<span style="color: white;">TLSHandshakeTimeout:</span> <span style="color: #0086f7; font-weight: bold;">30</span> <span style="color: white;">*</span> <span style="color: white;">time.Second,</span>
<span style="color: white;">}</span>
<span style="color: white;">client</span> <span style="color: white;">:=</span> <span style="color: white;">&http.Client{</span>
<span style="color: white;">Transport:</span> <span style="color: white;">tr,</span>
<span style="color: white;">Timeout:</span> <span style="color: white;">time.Second</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">10</span><span style="color: white;">,</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
This is a modification I made to the most intensively-used connection set; I didn't move the transport's scope for a far less-used connection in another function, figuring that yes, they would pile up to a degree, but they should properly close and age out as closed connections. This set will hammer the server with thousands of connections in parallel.<br />
<br />
This basically added some sane timeouts to functions that previously did not have any timeouts. This helped noticeably reduce my ghost connections disappear.<br />
<br />
<h2>
Remove a Hindrance, Create a New One</h2>
<div>
<br /></div>
The initial run finished a few days later. I realized that there was a bug in my loop logic. There were some bad words uttered and an updated version compiled.<br />
<br />
At this point we also moved the utility, and the volume to which data was being saved, to the same system that held the API endpoint server. Basically the server being queried for information was now also hosting the client requesting and processing results from the API queries.<br />
<br />
This eliminated what before was creating a kind of natural bottleneck that throttled performance; hundreds of connections per second simultaneously hitting the server but separated by the network transit time. Sure, it was on the scale of tens of milliseconds (if things were working well), but it really added up.<br />
<br />
Now the client was requesting it from the localhost. *Bam*. Within a few moments, the number of open connections (using netstat |wc -l, since I only needed a rough estimate) ballooned to 40,000 connections before this appeared on the console:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">dial tcp <ip address redacted>: can't assign requested address</span><br />
<br />
Because dial was in the error, it was most likely the client causing the issue. After some poking around, I ended up making two more changes.<br />
<br />
First, I tried to make a change to the number of idle connections the client keeps open. The default is two; more than that, and the client was closing the connections in the idle pool instead of making more efficient use of re-using the clients. Again, working with random connections aren't so bad, but hammering the same IP will highlight the need to alter this (and you probably don't want to change this if you're not making a large number of frequent calls to the same host):<br />
<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"> <span style="color: white;">tr</span> <span style="color: white;">:=</span> <span style="color: white;">&http.Transport{</span>
<span style="color: white;">Dial:</span> <span style="color: white;">(&net.Dialer{</span>
<span style="color: white;">Timeout:</span> <span style="color: #0086f7; font-weight: bold;">30</span> <span style="color: white;">*</span> <span style="color: white;">time.Second,</span>
<span style="color: white;">}).Dial,</span>
<span style="color: white;">TLSHandshakeTimeout:</span> <span style="color: #0086f7; font-weight: bold;">30</span> <span style="color: white;">*</span> <span style="color: white;">time.Second,</span>
<span style="color: white;">MaxIdleConnsPerHost:</span> <span style="color: white;">intIdleConns,</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
The changed setting is MaxIdleConnsPerHost in the transport. Here I set it to a variable that in turn is set from the command line so I could tune it at runtime, but instead of the default 2 I set it closer to 400.<br />
<br />
The next change was an alteration on the host server. There is some guidance on <a href="https://stackoverflow.com/questions/410616/increasing-the-maximum-number-of-tcp-ip-connections-in-linux" target="_blank">a SO question</a> explaining some tuning tweaks, but the gist of the change I made is this...<br />
<br />
When the TCP connection is made, the connection is made to an ephemeral port. When I have a ton of tcp connections hitting the server, it would starve the number of ephemeral ports available. The next step was to try increasing the number of ports available, and then the server could support more connections per second, hopefully at a level where the connections would close and age out properly before overloading the system.<br />
<br />
In this case, I changed net.ipv4.ip_local_port_range from "32768 61000" to "9000 64500". From the SO question, this means I changed the connectivity from (61000-32768)/60 = 470 sockets/second to (64500-9000)/60 = 925 sockets/second.<br />
<br />
There was another change I could make from the page that involved changing the net.ipv4.tcp_fin_timeout setting, along with a couple of others. I avoided that, opting instead to test these changes because the tuning advice was more like "change this on the client" or "change this on the server", not really geared to a situation where the server and client were eating resources on the same host. Making minimal changes to keep it working, for this project, would be fine.<br />
<br />
I ran netstat in a loop while the application ran again. This time the open connections quickly climbed to 70,000 connections before leveling out, and it held steady. After 15 hours of elapsed runtime, it had 3 connection errors show up. Otherwise it kept up with the load just fine.<br />
<br />
I should also mention that I ran 4 parallel processing tasks, one for each core. When I boosted that number it seemed to be a hindrance to the processing speed; keeping it at 4, the estimated processing speed was over 100K records/minute, easily holding sustained bursts 5 or 6 times the processing speed when the client was on a separate machine.<br />
<br />
<h2>
This Was a Minimal Set of Changes</h2>
<div>
<br /></div>
<div>
There were a number of lessons learned so far; above the basic novice checking that connections are properly read from a network client response before calling Close(), be aware that the transport is what controls the pool of connection clients for efficient re-use. </div>
<div>
<br /></div>
<div>
Next, be aware that by default timeouts are missing from the transport and client. Add them. </div>
<div>
<br /></div>
<div>
Also if you're hitting a particular server or set of servers with requests, change your MaxIdleConnsPerHost. Otherwise you're wasting connection use.</div>
<div>
<br /></div>
<div>
Last, an easy way to boost connection rates is to increase the number of ephemeral ports available. There are limits to this...and you don't want to starve other resources by taking away those ports from other clients or servers on the host.</div>
<div>
<br /></div>
<div>
There are plenty of other changes that can be made to increase horsepower of your servers. Some additional changes are in the SO question I linked to; another good blog post discusses how MigratoryData <a href="https://mrotaru.wordpress.com/2013/10/10/scaling-to-12-million-concurrent-connections-how-migratorydata-did-it/" target="_blank">scaled servers to 12 million concurrent connections</a>. I'd only caution that not every task requires this kind of engineering and you might want to exercise restraint in changing things when a few tweaks can accomplish decent performance for your use case. </div>
<div>
<br /></div>
<div>
Performance is a scale. Some things can be overcome with throwing lots of hardware at it. Sometimes a few tweaks will make your app run 5 or 6 times faster. </div>
<div>
<br /></div>
<div>
Happy tuning!</div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-69144015998245452102017-07-20T20:43:00.000-04:002017-07-20T20:43:03.083-04:00Golang: HTTP Client Opens Too Many Sockets ("Too many open files")This relates to a project that is work related, so I have to fuzz some of the details. But on the other hand, some details are naturally fuzzed because I have to remember some of the details and my memory is naturally fuzzy...<br />
<div>
<br /></div>
<div>
I'm working on a utility that is, on the surface, simple. It makes a call to an API endpoint using the http.Client, compares some quick results, and if certain conditions are met it makes a series of API calls to save the JSON responses.</div>
<div>
<br /></div>
<div>
The processing/checking process is carried out in a set of goroutines because the comparisons are easy to do in parallel. If the routine needs to pull a JSON reply, it calls a function that is laid out pretty much like the standard examples from the "here's how you get a web page in Go!" sites.</div>
<div>
<br /></div>
<div>
<div>
<!-- HTML generated using hilite.me --><br />
<div style="background: #f0f0f0; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="color: #007020; font-weight: bold;">func</span> GetReply(strAPI <span style="color: #902000;">string</span>, strServer <span style="color: #902000;">string</span>) <span style="color: #902000;">string</span> {
<span style="color: #60a0b0; font-style: italic;">// The URL to request</span>
strURL <span style="color: #666666;">:=</span> strServer <span style="color: #666666;">+</span> <span style="border: 1px solid #FF0000;">“</span><span style="color: #666666;">/</span>service<span style="color: #666666;">/</span><span style="color: #4070a0;">" + strAPI</span>
<span style="color: #4070a0;"> // Also add timeouts for connections</span>
<span style="color: #4070a0;"> tr := &http.Transport{</span>
<span style="color: #4070a0;"> Dial: (&net.Dialer{</span>
<span style="color: #4070a0;"> Timeout: 5 * time.Second,</span>
<span style="color: #4070a0;"> }).Dial,</span>
<span style="color: #4070a0;"> TLSHandshakeTimeout: 5 * time.Second,</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> client := &http.Client{</span>
<span style="color: #4070a0;"> Transport: tr,</span>
<span style="color: #4070a0;"> Timeout: time.Second * 10,</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> // Turn it into a request</span>
<span style="color: #4070a0;"> req, err := http.NewRequest("</span>GET<span style="color: #4070a0;">", strURL, nil)</span>
<span style="color: #4070a0;"> if err != nil {</span>
<span style="color: #4070a0;"> fmt.Println("</span><span style="border: 1px solid #FF0000;">\</span>nError forming request: <span style="color: #4070a0;">" + err.Error())</span>
<span style="color: #4070a0;"> return ""</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> req.Header.Set("</span>Content<span style="color: #666666;">-</span>Type<span style="color: #4070a0;">", "</span>application<span style="color: #666666;">/</span>json<span style="color: #4070a0;">")</span>
<span style="color: #4070a0;"> req.Header.Set("</span>Accept<span style="color: #4070a0;">", "</span>application<span style="color: #666666;">/</span>json<span style="color: #4070a0;">")</span>
<span style="color: #4070a0;"> // Get the URL</span>
<span style="color: #4070a0;"> res, err := client.Do(req)</span>
<span style="color: #4070a0;"> if err != nil {</span>
<span style="color: #4070a0;"> fmt.Println("</span><span style="border: 1px solid #FF0000;">\</span>nError reading response body: <span style="color: #4070a0;">" + err.Error())</span>
<span style="color: #4070a0;"> if res != nil {</span>
<span style="color: #4070a0;"> res.Body.Close()</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> return ""</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> // What was the response status from the server?</span>
<span style="color: #4070a0;"> var strResult string</span>
<span style="color: #4070a0;"> if res.StatusCode != 200 {</span>
<span style="color: #4070a0;"> fmt.Println("</span><span style="border: 1px solid #FF0000;">\</span>nError reading response body, status code: <span style="color: #4070a0;">" + res.Status)</span>
<span style="color: #4070a0;"> if res != nil {</span>
<span style="color: #4070a0;"> res.Body.Close()</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> return ""</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> // Read the reply</span>
<span style="color: #4070a0;"> body, err := ioutil.ReadAll(res.Body)</span>
<span style="color: #4070a0;"> if err != nil {</span>
<span style="color: #4070a0;"> fmt.Println("</span><span style="border: 1px solid #FF0000;">\</span>nError reading response body: <span style="color: #4070a0;">" + err.Error())</span>
<span style="color: #4070a0;"> if res != nil {</span>
<span style="color: #4070a0;"> res.Body.Close()</span>
<span style="color: #4070a0;"> }</span>
<span style="color: #4070a0;"> return "</span><span style="border: 1px solid #FF0000;">"</span>
}
res.Body.Close()
<span style="color: #60a0b0; font-style: italic;">// Cut down on calls to convert this</span>
strResult = <span style="color: #007020;">string</span>(body)
<span style="color: #60a0b0; font-style: italic;">// Done</span>
<span style="color: #007020; font-weight: bold;">return</span> strResult
}
</pre>
</div>
</div>
<div>
<br /></div>
<div>
This is actually a modified version of what I've pulled from various tutorials and examples, adding more calls to Close() and doing a check for whether res is nil before performing that call in an error.<br />
<br />
I also added a timeout to the client because by default it is set to 0; no timeout. As you can probably guess this version was modified while troubleshooting.<br />
<br />
After a few hours of the application running we had alerts come in about failing functions on the production servers. When I opened logs I discovered a number of "too many files open" errors, and a developer on the call said there were over 18,000 socket connections on each of the balanced servers.<br />
<br />
The only difference was my use of this test program, so I killed it. The socket count fell.<br />
<br />
Welp...guess we found the cause. But why?<br />
<br />
There are a couple basics for beginners when using http.Client requests.<br />
1) Close the response body after reading.<br />
2) Clients are reused.<br />
3) If you defer a call to Close() (as this one originally did, and most tutorials show) the function should call Close() when the function returns. The modified sample I posted simply closes it after reading the Body and checking for errors.<br />
<br />
At first I thought it was due to clients not closing; they must close in order to be re-used. I traced the execution path a dozen ways and added more explicit Close()'s in error checks...but those errors were never printing anything during the run, so errors shouldn't be causing spill of sockets.<br />
<br />
I added timeouts to the client and dialer. While that didn't hurt and probably made things a little cleaner, it still didn't help the too many open files/sockets error.<br />
<br />
Another lead came from a close reading of a <a href="https://stackoverflow.com/questions/17948827/reusing-http-connections-in-golang" target="_blank">Stack Overflow answer</a>. The function is creating a new Transport, tr, with each call. That Transport is what holds the Clients pool for reuse. See where I'm going with that?<br />
<br />
Another answer on that page talked about creating a global client for his functions to reuse.<br />
<br />
The theme was scope of variables matters when dealing with what allows re-use. Because I'm hitting the same server repeatedly and the function kept re-instantiating the mechanism that was used to govern client re-use, the number of new connections and left-open sockets ballooned.<br />
<br />
My next move was to go to the goroutines that were in charge of processing the replies from the API endpoints and have them create Transport instances, then when they call the function they passed the Transport as a parameter.<br />
<br />
I uploaded the program to a remote system instance and re-ran it while watching netstat on the server and the client systems. After initially ballooning to about 4,000 connections it soon settled down to well under 100 connections (using netstat |wc -l).<br />
<br />
Takeaways:<br />
1) Modify the default client, and maybe the transport dialer, to add sane timeouts.<br />
2) If you're hitting the same server repeatedly, do it all within the same scope as your transport instantiation or create a transport and pass it as a parameter to functions so you optimize the re-use of the client pool<br />
3) Check that you properly close the response body so the client can be re-used. Check in error paths that it can be properly closed without panicking.<br />
<br />
What about separating not just the Transport, but also the Client, then passing the Client around as a parameter? I didn't test that because I wasn't sure how "goroutine-safe" that would be against race conditions, despite the one answer on that Stack Overflow that demonstrated using a global Client instance for use. It's possible it works fine. At this point it looks like passing the Transport worked fine, though.<br />
<br />
I'll also note that my usual self-loathing and insecurity isn't getting the better of me this time because the top answer on that question that inspired me to try this solution was the usual advice I found repeatedly in other sites and blogs (and SO answers); check that you close your response properly. It's the top answer by a significant margin. It was almost an afterthought to realize that maybe what I was doing was pummeling one particular website with multiple instantiations of Client pools so Client reuse was a minimum.<br />
<br />
Happy HTTP Client-ing!</div>
</div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com1tag:blogger.com,1999:blog-2854270733196620893.post-70353855768349042612017-07-13T18:32:00.000-04:002017-07-13T18:32:03.351-04:00Your Experiences Create Your MethodsSounds obvious, doesn't it?<br />
<br />
But at the same time, I feel like it's one of those things that shapes our worldview to the point where you lose sight of the fact that it's obvious; we end up taking our views for granted and ignoring why you approach problems the way you do.<br />
<br />
(Or, perhaps worse, we ignore why other people approach problems the way they do, which in turn you react towards them in a possibly negative fashion.)<br />
<br />
I'm thinking of this because the other day I was working with a coworker on a problem assigned to us by a manager. Without getting into too many details, one step involved a program reading a list from a text file.<br />
<br />
The file was tens of thousands of lines; the program expected the format:<br />
12345,string of text,state_name<br />
<br />
The file we got was formatted:<br />
12345,"string of text",state_abbreviation<br />
<br />
We were coming up with a game plan and reviewing steps when the file came in, and were divvying up the work needed to get the ball rolling.<br />
<br />
My very first thought was to write a Go program that read the file contents into a slice, range over the slice to replace the comma-quote and quote-comma with just commas, then split by comma and replace the item[2] with the full state name using a map I could copy and paste from a previous program I had worked on. Give the size of the file to work on, it shouldn't have taken too long, from my estimation.<br />
<br />
My coworker volunteered to reformat the file. After he completed it, I asked him how he cleaned the file. His background is ostensibly in sales, although he also does programming in PHP and can create mockups and web utilities for other employees to use in pulling reports and demos, so I expected he ran it through a one- or two-line PHP filter or something similar.<br />
<br />
He pulled up Excel, imported the file as a comma-delimited file, then showed me a formula that pulled state abbreviations-to-full-names from another spreadsheet he already had set up.<br />
<br />
In a way the approach wasn't too different. We split the lines into fields and used what amounted to a map to do a value replace, then export the results to a new text file. But the execution was very different. His sales experiences, and having to deal with formatting reports from our system, meant the first tool he used to solve the problem was a spreadsheet (which was a faster and more efficient solution than I was going to use for a one-off reformatting job like this.)<br />
<br />
I've been working heavily on Go-based utilities; manipulating log files, manipulating APIs, making text dance as it was processed through pipelines and sending results through databases and monitoring systems. When I saw this text file I immediately saw strings.Split and map[string]string solutions running through my head.<br />
<br />
What other solutions are there? Tons, no doubt. Filtering through a series of AWKs and pipes and redirects...maybe PERL...maybe PHP...I know there's plenty of people who would have used Excel to import it and alter it by hand. While I'd probably argue that the manual method could be considered "wrong", I'm equally sure there are people who would have arguments why every approach considered (or used) would have been "wrong."<br />
<br />
In the end it was the (timely) results that mattered.<br />
<br />
So next time you see someone with a different approach to doing something, don't be quick to criticize. Think about why that person has that approach. Maybe they do know something that is more efficient. Maybe not. Sometimes it's interesting to learn how someone came to use the methods they use and you'll learn something about what it's like for people who aren't you.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-91915005503413992642017-05-31T18:45:00.002-04:002017-05-31T18:45:47.428-04:00Programming a StargateI've really loved using the Go language. Part of my exploration and tinkering has involved side projects where I'd pull information from outside sources, usually websites, and parse the response for the information I'm looking for.<br />
<br />
I always try to be a good citizen for web scraping; I pull the minimum information I need, close connections once I get the response, insert delays between multiple page views, etc. I always try to put only as much load on a service as a regular user would when web browsing.<br />
<br />
"What does that have to do with Stargates?"<br />
<br />
I really like Stargate. SG-1, Atlantis, or Discovery, doesn't matter (except the animated series...I pretend that doesn't exist.)<br />
<br />
Some people hate it when geeks watch movies and get nitpicky about details. "CAN'T YOU JUST ENJOY THE MOVIE?!"<br />
<br />
Not always, no. When I enjoy something, I'm the type of person who enjoys not just the story, but the universe in which it is set; this means learning about the feasibility of that story universe. Oh, sure, there are some rules you have to accept in order for that story to work (such as faster than light travel magic handwaving or using lightsabers and not having them vaporize anything too close to the wielder since, you know, REALLY HOT PLASMA...)<br />
<br />
One of the key bits to Stargate involves using the Stargate; the dial home device for Earth's portal was not found with the gate. The device can, however, be manually "dialed", which is what SG command does...they have a computer control massive motors that sets each of the chevrons into a lock position, as well as reading diagnostic signals from the gate.<br />
<br />
The show handwaves a lot of this process away, but I think it's implied that someone had to program the computer to attempt dialing control and reading (and sending) signals to control the gate. It's a black box; they needed to figure out "If I do X, do I get Y?" and more importantly, "Do I get Y consistently?" (Then maybe figure out what Y means. I mean, you're screwing around with an alien device that connects to other worlds, after all...) I like to think about what it took for that person to approach that black box and coax information out of it in a way that was useful.<br />
<br />
Getting information from these websites, designed for human interaction using a web client, is like trying to programmatically poke a stargate. In the process I've discovered that our many websites are frustrating and inconsistent (I sometimes wonder, when I just want to get a list of text to parse, how many common websites are compliant for devices used by people with poor eyesight or braille systems.)<br />
<br />
For example, I tried looking at a way to query the status of my orders from a frequently used store site. I thought it would be simple...log in and pull the orders page. Nope. If you order too many items, you might have to query another page with more order details. Sometimes order statuses change in unexpected ways. The sort order of your items isn't always consistent, either. And those were the simpler problems I encountered...figuring out consistency in delivery estimate<br />
<br />
I tried a similar quick command line checker for a computer parts company. Turned out they had far more order statuses than I thought they did, and alerting me to changes in that order status was an interesting exercise in false alarms when they'd abruptly change from shipped to unknown and back again.<br />
<br />
Another mini-utility I worked on was checking validity of town locations. Pray you never have to work with FIPS...<br />
<br />
The website I chose seemed to be fairly consistent in the format of the information. Turns out I was naive in how various towns are designated, and this website was not internally consistent in showing information in a particular order. I get all sorts of interesting but very weird results for different areas around the country.<br />
<br />
I'm sure that if I had a <a href="https://en.wikipedia.org/wiki/Stargate_(device)#Dial-Home_Device" target="_blank">dial-home device</a> (in this case, a clear API to the websites or access to an internal database) these lookups would be more straightforward. As it stands, the closest API I can use is the same as anyone with a mouse and keyboard...parsing the web page.<br />
<br />
While frustrating at times, I am thankful that these mini-projects have taught me a few things.<br />
<br />
<ul>
<li>Websites, some of which I've routinely used, are not as standardized as I thought within their own site. I just hadn't noticed when I'm searching for particular information the items I click on to get what I'm searching for.</li>
<li>I end up rethinking a lot of parsing logic when digging and sorting through human language.</li>
<li>Web sites implement some seemingly convoluted logic for interacting with clients and I now have a new appreciation for web browsers.</li>
<li>I also have a new appreciation for the usefulness of a good API. If I start a business and there's anything that can be exposed through API, I'm making it available through an API.</li>
</ul>
<div>
<br /></div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-83267278904515478062017-04-29T16:45:00.002-04:002017-04-29T16:45:48.369-04:00Learning By Creating Support ApplicationsNot long ago I started a job with a company whose primary product is a very custom application that is comprised of many smaller interoperating applications. Without getting into too much detail, the applications communicate through various APIs, many of which are not well documented.<br />
<br />
(What follows are thoughts that are not focused solely on the new employer, but rather a set of experiences I've gathered over the years from several jobs and interactions with others in the technology field. In other words, this isn't about the current employer. It's a conglomeration of experiences, and it's my own opinion. Just figured I'd have to clarify that...)<br />
<br />
As a company focuses on growth, there comes a time when maintenance and monitoring is moved to staff that are dedicated to those tasks so the developers no longer have to do triple duty; for the new hire tasked with pioneering that position, gathering statistics to get a feel for the behavior of their systems over time, and taking care of regular maintenance and basic troubleshooting is very daunting when there is little (or no) documentation available outlining how to get the necessary metrics for gauging the health of the system.<br />
<br />
And it isn't just a lack of documentation that acts as an obstacle. When a software-based company is first conceived and grows, it's natural for the programmers to work on getting the product into a usable, testable state. This means overcoming problems as they arise and focusing on results, not laying framework for delegating future operations.<br />
<br />
That fosters institutional knowledge. The more of your system that is developed in-house, the more information future maintainers must glean about your system without help of outside references. Sites like Serverfault can help when you're trying to figure out why a new deployment of Nginx won't work, but it won't be useful when a log contains output from a Java application Bob, three desks away, wrote while debugging a particular reply encountered from another subsystem's API response.<br />
<br />
Small companies with a small number of developers may feel it is inconvenient to be interrupted by the new person's constant questions about why application A is dependant on application B, or how application C discovers a service status on server 3. As a new hire, I feel a little hesitant to approach others with these types of questions, preferring to try looking for answers through other means before taking someone else's time.<br />
<br />
(In my opinion, if the answer is to check the source code from the repo and read that to get the answers, you may as well have hired a new programmer; recognizing a need for someone dedicated to operating and maintaining your system outside the coterie of coders is a sign that there may be a need to dedicate time to documenting and tooling the application for non-programmer use.)<br />
<br />
How can a new hire get a grasp on this situation?<br />
<br />
In this case, I've been writing a series of Nagios plugins specifically configured to pull metrics from the various subsystems in the company application. There are cases where I thought a simple task was actually more nuanced that first appeared; each time, I ended up discovering something more about the operation of the system, and I made sure it was documented for later reference.<br />
<br />
Each time there's a failure case, I would make a note and start work on a new monitor so we'd know about it in the future. These monitors didn't just collect a snapshot of the current state of a service, it would gather some metric that was then sent to a database and from there plotted on a graphing application for performance monitoring.<br />
<br />
The current product relies on database performance; some queries behave different from others, where some are straightforward and others require processing of filters. Some of my checks measure response times.<br />
<br />
Others are querying API endpoints for replies of what the services believe are their current health states.<br />
<br />
Some queries are pulling the status of database indexing.<br />
<br />
In cases where the application is exposing information through Java beans, my plugins are pulling numbers from JMX and checking for values within established expectations.<br />
<br />
In other cases, plugins are checking for the existence of files that are supposed to be regularly updated and when certain records are updated in the database.<br />
<br />
Each of these plugins, once finished and deployed, are being documented for operation in a way that when new people are hired he or she should be able to easily find a list of how these work and gather indirect information on some aspects of the in-house application operation without programmer-level institutional knowledge.<br />
<br />
In the case of my new position, I've gained a higher respect for the value of meta-applications in gaining insight on how a complicated system works. Having information written out or explained to you is enlightening, and I never feel that documenting how something works is a waste of time. But until you find yourself executing on that knowledge, I'm not sure you really understand the subject. Creating support applications that meaningfully interact with the system pushes knowledge into the realm of wisdom the way reading about the science of flight comes alive after building your first remote control plane.<br />
<br />
When confronted with the task of comprehending the colossal, try learning about the limited first with applications that monitor and interact with small aspects of the system. Not only will others benefit with the support applications, but you'll benefit with the mental exercise and in the end have a better model of how everything works!Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-22191430256531888112017-03-23T19:09:00.000-04:002017-04-29T15:00:06.714-04:00Golang: Remember This When Using Select For Monitoring ChannelsI thought I would share something that's easy to overlook when using a loop to listen for messages from channels in Go.<br />
<div>
<br /></div>
<div>
The following is a simple code snippet:</div>
<div>
<br /></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">for {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> for a := range structOfChannels {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> select {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> case msg := <- structOfChannels[a].Chan1:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> // Something</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> case msg := <- structofChannels[a].Chan2:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> // Something</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> default:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">All this is doing is rotating over a series of channels for a message and processing them. The default case makes sure the loop doesn't get stuck after the first iteration of "select", and the for without conditions means continue until eternity.</span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">I noticed, when running Activity Monitor (this was using Go 1.8 on OS X) that the processor would stay near 100%. The system seemed responsive, but the processor staying that high was, to me, annoying.</span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">The solution was simple; make the loop wait a fraction of a second each iteration.</span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;"><br /></span></div>
<div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">for {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> tmTimer := time.NewTimer(time.Millisecond * 50)</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> <-tmTimer.C</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> for a := range structOfChannels {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> select {</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> case msg := <- structOfChannels[a].Chan1:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> // Something</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> case msg := <- structofChannels[a].Chan2:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> // Something</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> default:</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"> }</span></div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;">}</span></div>
</div>
<div>
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">This just makes the loop wait 50 milliseconds before ranging again, a pause smaller than most humans would perceive but enough for the computer that it dropped the processor use to near nothing. </span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">There are a few other approaches that work, but have a similar effect. For example, if you're worried about the overhead of creating and freeing the NewTimer(), you could create a NewTicker() outside the for{} scope and keep reusing that. You can also probably lower the milliseconds to smaller values and see where the processor starts kicking up, but I'll leave that to the reader to experiment and tune.</span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;"><br /></span></div>
<div>
<span style="font-family: "times" , "times new roman" , serif;">The point is, because the system seemed responsive, it was easy to overlook the effect of a simple for{} loop used to monitor messages from goroutines and there's a possibility this could have an effect when deploying to servers. Check your performance when testing your work!</span></div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-49635449161380437902017-03-06T11:24:00.001-05:002017-03-06T11:26:38.629-05:00When To Use "+" And When To Use "%20": Adventures In URL EncodingI've been working on some Go-based utilities to interact with a website application written in Java. Part of this involves, in many cases, encoding database queries that are submitted to API endpoints on the Java application and interpreting the returned results.<br />
<br />
In the process I learned something new about encoding easier to read/more human-like strings to encoded strings for the server. Namely, the standards seem broken.<br />
<br />
I jest, but really the trouble was a matter of "it works if you know specifically how to make it work for this case."<br />
<br />
My workflow would involve a Curl command line from a coworker with a library of working queries he had scripted out for use in other situations. I'd take that and translate it into the utility or Nagios plugin I was writing.<br />
<br />
I took the string used in the Curl sample and feed it into Go's url.QueryEscape(), then send it to the database endpoint with<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">req, err := http.NewRequest("GET", strURL+"?q="+strQuery, nil)</span>
<div>
<br /></div>
...which promptly spit back a syntax error. Huh?<br />
<br />
A little digging later and I found that there are standards defining an encoded space can be either "+" or "%20". And it wasn't necessarily clear when each was acceptable, and different languages varied in the strictness of their interpretation of standards.<br />
<br />
The first red flag here is that language encoding libraries implement these changes differently, but I still felt kind of stupid at first not knowing what I was doing wrong. My self-flagellation eased a bit when I saw this was even <a href="https://github.com/golang/go/issues/4013" target="_blank">a bug report for Go</a>. It didn't go anywhere in terms of changing things; the language still encoded spaces as pluses and not percent-20's, but it at least acknowledges that I'm not the only one scratching my head why it wasn't working as expected.<br />
<br />
A more elaborate answer was <a href="http://stackoverflow.com/questions/1634271/url-encoding-the-space-character-or-20" target="_blank">found on Stack Overflow</a>. It wasn't the top answer, although each gave some elaboration on the issue, but the best one boiled the situation down to the existence of different standards for what part of the URI was being used, and<b> backwards compatibility meant that %20 is generally safest to use but technically the %20 should be used before the ? in a URI and + be used after the ? for denoting a space.</b><br />
<br />
In my case, Go liked the + for escaping strings and eschewed the percent-20. My fix? Right after running the url.QueryEscape():<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">strQuery = strings.Replace(strQuery, "+", "%20", -1)</span><br />
<div>
<br /></div>
Not the most elegant, but when I submitted that strQuery, the Java application was happy!<br />
<br />
My takeaways:<br />
1) Something I thought was simple...feeding a string to an escape function for encoding properly to a URI format...isn't necessarily straightforward. If you have trouble, find out if your application is expecting pluses or %20's for spaces.<br />
2) Computers are binary...it works or it doesn't. But implementations of standards are still influenced by people, and languages (and libraries) are implemented by people, so even given the constraint of binary...people still make things more complicated in practice.<br />
3) Given the confusion of + versus %20 when searching around online, I'm not the only one having this kind of issue.<br />
4) Just use %20. Unless I run into a specific case where the other side isn't translating %20 correctly.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-59269574169532118152016-12-07T16:13:00.002-05:002016-12-07T16:13:45.063-05:00Creating a Test Ensemble with ZooKeeper and VirtualBoxWhat is <a href="https://cwiki.apache.org/confluence/display/ZOOKEEPER/ProjectDescription" target="_blank">ZooKeeper</a>? It's an Apache project creating a server that allows distributed information and message storage for distributed processes. If you have a number of servers that need to coordinate certain information, ZooKeeper might be useful, especially if your project uses Java.<br />
<br />
The interface is very reminiscent of a simple filesystem, using "znodes" as files that can have sub-znodes containing more information. In addition to passing and storing small bits of information (less than a megabyte per node, as I recall), znodes can also be set to ephemeral, so when a server connects to the ZooKeeper ensemble (cluster), a znode is registered and other servers can find that server. When the server goes offline, the znode disappears, so your application can be informed (if it sets a watch on that znode) if that server is no longer available to the cluster or it can search for the available servers (znodes) before connecting to that system.<br />
<br />
That's the simplest overview.<br />
<br />
I created 3 ZooKeeper nodes using VirtualBox.<br />
<br />
I first installed 1 Ubuntu-Server VM. I named it Cluster1 for the VM name and hostname, used Ubuntu Server 64 bit with version 16.10. The VM had an 8GB drive (sparse drive so it didn't eat all the space on my workstation right away), 1GB RAM and bridged ethernet.<br />
<br />
While running through the install I added the SSH server package when prompted.<br />
<br />
Once the VM was running I ran<br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get update</span><br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get upgrade</span><br />
<br />
I also ended up running<br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get dist-upgrade</span><br />
<br />
At that point it no longer had packages to update nor packages held back.<br />
<br />
I shut down the VM and told VirtualBox to clone it. The first was named Cluster2 and the second clone became Cluster3. During the clone wizard step-through I told VB to reinitialize the MAC addresses on the network cards and do a full clone so these are independent VMs.<br />
<br />
I fired up Cluster2 and changed the hosts file and hostname file in /etc to reflect the fact that the machine is cluster2, not cluster1, then repeated the step for Cluster 3. A restart of the two machines should now show the proper names for the machines.<br />
<br />
Now I have 3 small servers running. In each of them, I ran<br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get install zookeeper</span><br />
<br />
I edited the /etc/zookeeper/conf/myid file so cluster1 had the value 1, cluster2 had the value 2, and cluster3 had 3. In the /etc/zookeeper/conf/zoo.cfg file, I added the IP's for each of the three machines reflecting the 1,2, and 3 values, like so (just for the specify zookeeper servers section):<br />
<span style="font-family: Courier New, Courier, monospace;">server.1=192.168.254.1:2888:3888</span><br />
<span style="font-family: Courier New, Courier, monospace;">server.2=192.168.254.2:2888:3888</span><br />
<span style="font-family: Courier New, Courier, monospace;">server.3=192.168.254.3:2888:3888</span><br />
<br />
I used the IP's for each server because I didn't edit any hosts file or local DNS to allow finding these ZooKeeper servers by name, although it could certainly be done. On the other hand, using the IP means no DNS lookup, so I might have shaved a few milliseconds off communications.<br />
<br />
The default install didn't have any service scripts, so "service zookeeper restart" leaves Ubuntu scratching it's head at you. Install some add-on scripts using:<br />
<span style="font-family: Courier New, Courier, monospace;">sudo apt-get install zookeeperd</span><br />
<br />
At this point I can run<br />
<span style="font-family: Courier New, Courier, monospace;">sudo service zookeeper restart</span><br />
<span style="font-family: Courier New, Courier, monospace;">sudo service zookeeper status</span><br />
<br />
A basic ensemble (or cluster) should now be running!<br />
<br />
How do you test this...or at least do something with it? There's a Java CLI tool included with ZooKeeper, but it turns out there's a bug where a particular environment variable isn't set. It's not a big deal...just set it before trying to run the tool.<br />
<span style="font-family: Courier New, Courier, monospace;">export JAVA=java</span><br />
<br />
Now you can run the tool. This will launch it, and connect to a local server instance.<br />
<span style="font-family: Courier New, Courier, monospace;">/usr/share/zookeeper/bin/zkCli.sh -server 127.0.0.1:2181</span><br />
<br />
From here, you can use the "help" command to get a list of available commands. To just kick the tires a little, I ran these commands:<br />
<span style="font-family: Courier New, Courier, monospace;">ls /</span><br />
<span style="font-family: Courier New, Courier, monospace;">create /zk_test My_Data</span><br />
<span style="font-family: Courier New, Courier, monospace;">ls /</span><br />
<span style="font-family: Courier New, Courier, monospace;">get /zk_test</span><br />
<span style="font-family: Courier New, Courier, monospace;">set /zk_test test</span><br />
<span style="font-family: Courier New, Courier, monospace;">get /zk_test</span><br />
<span style="font-family: Courier New, Courier, monospace;">delete /zk_test</span><br />
<span style="font-family: Courier New, Courier, monospace;">ls /</span><br />
<span style="font-family: Courier New, Courier, monospace;">quit</span><br />
<br />
And as I ran through the list of commands (creating the zk_test znode, seeing the data stored as the string "My_Data", setting the data to "test", and finally deleting the znode) I would list and set information from different VMs to see that the data was synchronizing properly.Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-43381336110624291522016-11-10T18:22:00.000-05:002016-11-10T18:22:03.524-05:00Time Machine With File Vault Corruption: Reformatting the External DriveI've already written one post that went into detail about reformatting an external drive that acted as my Time Machine backup for my Mac. External USB drives can be bumped and the cables loosened, raising the chances that data corruption will occur; encrypted drives are <i>really</i> cranky when that happens.<br />
<br />
In my case, reformatting and starting over is acceptable for recovering and getting the system backed up again as long as there isn't any indication that the hardware itself is failing.<br />
<br />
This time around any time I tried accessing the data was met with failure; from the diskutil command line utility, it seemed that there was an encrypted volume being found and mounted by the system (even though it wouldn't appear in Finder nor in Disk Utility...Disk Utility kept hanging with a spinning beach ball and wouldn't show any drives mounted at all when it launched...). Diskutil seemed to show the existence of an unlocked encrypted volume on my Time Machine drive, but it had no data, and attempts to reformat the drive using the command line returned an error to the effect of "resource busy."<br />
<br />
After several attempts to read the volume I decided to try just obliterating the data on the drive by wiping as many sectors as I could at the beginning of the drive. Unfortunately, attempting to do that returned a resource busy response. A daemon on OS X was trying to access the drive, and that prevents direct access to the device.<br />
<br />
But there is a way around it.<br />
<br />
Use this command to identify your external drive (the Time Machine drive, in my case)<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">diskutil list</span><br />
<br />
Su to root.<br />
<br />
<span style="font-family: Courier New, Courier, monospace;">sudo su</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">Disconnect the drive from the USB port. Then use the up arrow and enter key to repeat this command when plugging in the drive again. The goal is to execute this command just after the system sees it, but before the auto-mount daemon tries to be helpful and prevent your access...replace "disk1" with with the correct disk number found with the diskutil command above. <b>Triple check that you have the correct drive. If you overwrite the wrong drive you will be very unhappy and it's not my fault.</b></span><br />
<span style="font-family: Times, Times New Roman, serif;"><b><br /></b></span>
<span style="font-family: Courier New, Courier, monospace;">cat /dev/random > /dev/disk1</span><br />
<span style="font-family: Courier New, Courier, monospace;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">This will overwrite data on the drive with random gibberish. Once enough sectors...like the partition table...the drive will be seen as ready to be formatted by OS X. This process is not going to give much feedback...and since the drive is large, it could take forever to complete. I advise letting it run for several minutes then use control-C to abort the command.</span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">At this point I used Disk Utility to format the drive, then opened Time Machine to remove the previous backup drive and re-add the "new" one.</span><br />
<span style="font-family: Times, Times New Roman, serif;"><br /></span>
<span style="font-family: Times, Times New Roman, serif;">And yes, I re-encrypted it. I'd rather not let the backups be readable by others, despite the risk of corruption wiping the backup, and creating a new set of backups took only about a day to complete.</span>Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-5648506041511491602016-10-25T11:16:00.002-04:002016-10-25T11:16:42.638-04:00Apple Remote Desktop (ARD) Can't Find MachinesOne of ARD's more entertaining tricks is "forgetting" machines on the network. I'm still not sure what triggers this, but it certainly is among the more annoying behaviors to crop up.<div>
<br /></div>
<div>
There are a couple of sites that mention this kind-of sort-of trick to kick ARD in the head, but I thought I'd make a note here for my own quick reference in one place. These notes should work on El Capitan (10.11) and Sierra (10.12).</div>
<div>
<br /></div>
<div>
Summary: Remove cached settings from ARD, remove network DNS/ARP caches on machine, kick ARD in the head...</div>
<div>
<br /></div>
<div>
<ol>
<li>In ARD, go to the All Computers list, highlight the machine names and delete them.</li>
<li>Quit ARD.</li>
<li>Flush DNS cache: <span style="font-family: Courier New, Courier, monospace;">sudo dscacheutil -flushcache;sudo killall -HUP mDNSResponder</span></li>
<li>Flush ARP: <span style="font-family: Courier New, Courier, monospace;">sudo arp -ad</span></li>
<li><span style="font-family: Times, Times New Roman, serif;">Kick ARD in the head by restarting the ARD agent (on clients): </span><span style="font-family: Courier New, Courier, monospace;">/System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -restart -agent</span></li>
</ol>
<div>
<span style="font-family: Courier New, Courier, monospace;"><br /></span></div>
</div>
<div>
<span style="font-family: Times, Times New Roman, serif;">Start Remote Desktop again and re-scan the network. Because the clients were removed, attempting to view/connect may require you to re-enter credentials.</span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;"><br /></span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;">Also keep in mind I noted testing this on El Capitan and Sierra. Another annoyance with OS X releases is that the syntax/procedure for flushing DNS changes alarmingly often, so it may take some Googling if your release is different.</span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;"><br /></span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;">The last note I have is that if this doesn't work, check that a network hiccup didn't force the client's wireless to shrug its shoulders and give up, meaning that the actual problem all along was that the client was not able to be remotely managed over the network all along.</span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;"><br /></span></div>
<div>
<span style="font-family: Times, Times New Roman, serif;">Whoops.</span></div>
Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-78745650825718930632016-09-21T11:27:00.001-04:002016-09-21T11:27:25.325-04:00Skittles are to Refugees what M&Ms are to Not All MenSide note: I can hardly believe how long it's been since I've added to this blog...but it looks like several months have flown by since my last entry. I guess I took an impromptu blog break while I was heads-down on some personal Go programming projects. Amazing how something can expand to fill your spare time activities...now I have programming plus personal issues to nudge me into remedying my blog hiatus status...<br />
<br />
This entry is not a Go-related topic. This, instead, is an entry about a Presidential candidate's campaign assertion that, when I heard about it, felt eerily familiar.<br />
<br />
Donald Trump, Jr. used the recent bombings in Chelsea and New Jersey to compare refugees to <a href="http://nypost.com/2016/09/20/donald-trump-jr-compares-refugees-to-poisoned-skittles/" target="_blank">a bowl of perhaps-poisoned candy</a>. Basically, the argument goes, if you have a bowl of Skittles and 3 of them were poisoned, would you take a handful?<br />
<br />
The makers of Skittles were not amused, as you can imagine. <a href="https://twitter.com/SethAbramovitch/status/778091768793407488/photo/1?ref_src=twsrc%5Etfw" target="_blank">Their reply</a> simply asserted that Skittles are candy while refugees are people, so they did not believe the analogy was proper (side note: did you know Skittles is owned by Wrigley Americas? I thought they were known for gum...)<br />
<br />
Leaving aside the argument that the suspect in the bombing is a <a href="http://www.usatoday.com/story/news/nation/2016/09/19/rahami-profile-bombings-chelsea-new-jersey/90687224/" target="_blank">naturalized American citizen</a> or that the actual math behind your odds of dying at the hands of <a href="http://www.wendyperrin.com/7-keys-traveling-without-fear-despite-terrorist-attacks/" target="_blank">terrorism in the US are minuscule</a> compared to heart disease, being struck by lightning, car accident or, in the US, being shot, hearing this tweet make the rounds in the usual social media reminded me of another "would you want to risk <eating a large amount of innocuous, common food> if you <b>knew</b> there was a <tiny but acknowledged number> that were deliberately fatal?" meme, only for the opposite, pro-social justice argument. It wasn't hard to uncover it.<br />
<br />
Apparently the Trump campaign was resurrecting the old <a href="http://www.bustle.com/articles/184981-donald-trump-jr-ripped-off-this-feminist-mm-meme-from-2014" target="_blank">"Not All Men" argument that used M&M's</a>, instead of Skittles, in response to the idea that not all men are terrible, so please don't overgeneralize about all men being <murderers || rapists || chauvinist pigs || etc>. It seemed to make sense...they acknowledge that not all men are terrible, but all you're doing is derailing the actual point by trying to deflect on focusing on the population of people that were good instead of the very real danger of the significant population of men doing bad things. I had forgotten that meme...and only realized now that it seems to have largely disappeared from the social media rounds. Or perhaps I had simply stopped paying attention to the waves of regurgitating hive-thoughts posing as original thought...<br />
<br />
Thanks to the anti-Trump sentiment, though, this iteration of the poisoned candy argument didn't last long before a rebuttal made the rounds. Now the small-population-of-poison-in-the-patch argument is linked to <a href="https://thinkprogress.org/donald-trump-jr-skittles-tweet-a48b0bdce9bd#.s1as598ew" target="_blank">anti-semitic material from Nazi Germany</a>. In the heartwarming story <i><a href="https://en.wikipedia.org/wiki/Der_Giftpilz" target="_blank">Der Giftpilz</a></i>, Jews are compared to mushrooms in the forest, where there are good people and good mushrooms, and there are bad people and bad mushrooms, and bad mushrooms can kill whole families...so you have to be vigilant against poisonous Jews killing your family. The author, <a href="https://en.wikipedia.org/wiki/Julius_Streicher" target="_blank">Julius Streicher</a>, was executed as a war criminal.<br />
<br />
Oversimplifying to the point of overgeneralization (ironically, in the case of what I'm about to say) is rarely, if ever, effective when analyzed. It is a propaganda tool; a way to get eyeballs with a headline without actually having a headline. In the cases here, these were used as tools to manipulate people using what seemed, at first thought (and rarely a second thought applied) logical, sound reasoning. It takes more thought to understand the nuances of the actual issues involved...and these shortcut-think-phrases are simply a way to appeal to lazy supporters of side X, and to possibly deflect from the actual goal or reasoning behind a movement or idea.<br />
<br />
In the case of the poisoned candy, if you're told there are definitely, say, 3 poisoned items in there, of course reasonable people are not going to eat them (or in some variants, feed them to their kids.)<br />
<br />
Of course it ignores that candy are not individual people with complex, nuanced personalities.<br />
<br />
It ignores that a reasonable person has little reason to believe that any candy are poisoned in your average bag of bulk candy.<br />
<br />
Or that the actual odds...the math that we, as human beings with minds poorly wired to think in terms of mathematics and statistics,...are nowhere near the same for dying from terrorism as they are for "3 of <a bowl> of candy" are for killing you, unless the bowl were perhaps a swimming pool or you apply the analogy to something purposely vague so every jackass making a sexist or unwanted comment to a passing stranger counts as a poisoned candy.<br />
<br />
It also ignores the ethical motivations of the rest of that candy bowl...that they're people, searching for safety, fleeing a war they had nothing to do with, and the vast majority want nothing more than to live their lives in reasonable safety.<br />
<br />
And it ignores the possibility that the candy is loaded with sugars that contribute to the diabetes and heart disease that are more likely to kill you than terrorism despite the "good" label applied to them.<br />
<br />
And it certainly doesn't acknowledge that there is no binary "safe vs. unsafe" activity in life. I often wondered this when a religious person would talk about the evils of gambling...isn't life a gamble? You're getting out of bed without thinking that the shift in blood pressure could trigger a stroke, and taking a morning shower without acknowledging that you could slip and fall and crack your head. <a href="https://en.wikipedia.org/wiki/Toilet-related_injuries_and_deaths" target="_blank">The act of taking a number two can strain your heart and cause a heart attack</a>. Eating a meal can cause you to choke to death. There is no %100 safe activity for which you're not betting that you'll be okay performing a relatively common thing, and if gambling is the act of wagering on an uncertain outcome, life is filled with uncertain outcomes.<br />
<br />
In the end, I'm not indicting the social movements that led to these memes. My post is an indictment against the type of thinking that leads people to treat these thought-bites as if they were entire arguments for or against an idea instead of the bullet points they really are; we are a culture that mistakes sound bytes and headlines for actual news items, when the actual story requires actual research of some depth to even begin to understand and empathize with.<br />
<br />
Worse, we have so much information, so many sound- and thought-bites begging for our attention that people (and media outlets) treat stories like the <a href="http://www.cnn.com/2016/09/20/entertainment/angelina-jolie-brad-pitt-divorce/" target="_blank">recent Angelina Jolie and Brad Pitt divorce</a> filing as something more deserving of headlines than a gossip-column footnote.<br />
<br />
Perhaps this is also a reflection of how people process information; perhaps before, we didn't have the technology to indulge in sifting through a plethora of visual clickbait and having the luxury of ignoring nuance. Or perhaps people have always been full of uninformed opinions, but now we are graced with social media giving a voice by which to proclaim these ideas. How much we are shaping our information and media tools versus how much we are shaped by them is an exercise for philosophers and time to measure.<br />
<br />
Unfortunately I can't pretend to be above the influence; I can only try to acknowledge that it happens and try to limit the degree of validity I assign to the resulting fallacies. The best thing I've done is limit my exposure to social media, and even popular news outlets. I've gradually cut things out that others take for granted; as satellite (and cable) TV grew more expensive and we tried to cut bills, we stopped watching TV (and I am still amazed at how little tolerance I have for commercials as a result). I configured Twitter to dump tweets directly to Facebook, eschewing having to sift and post there in order to update virtual relatives and friends of life events and thus limiting the amount of regurgitated cruft from the FaceBook timeline that inevitably led to a "Here's a Snopes article that had you spared 5 minutes to Google you'd have known what you just said was pure crap" reply.<br />
<br />
So take a minute and reflect on the true meaning of a soundbite. What is the truth behind it? What is the possible true motivation behind the meme? And most of all, why are you willing to support, or fight, that meme?<br />
<br />Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-917485173625000232016-05-01T18:50:00.000-04:002016-05-01T18:50:28.402-04:00GoRoutines: Are They a Tree, or Independent?I was working on a side project when I ran into a question regarding goroutines spawning goroutines; if you have spawn a goroutine from main() (I'll call it Offspring1), and Offspring1 spawns a goroutines called Offspring2, then Offspring1 returns(), what happens to Offspring2?<br />
<br />
Does it die, like pruning a branch off a process tree?<br />
<br />
Or does Offspring2 keep running?<br />
<br />
I wrote a small test application to find out.<br />
<br />
<span style="font-size: large;"><b>The Test:</b></span><br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<table><tbody>
<tr><td><pre style="line-height: 125%; margin: 0;"> 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61</pre>
</td><td><pre style="line-height: 125%; margin: 0;"><span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"time"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">chanRunner2</span> <span style="color: white;">=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">chanRunner1</span> <span style="color: white;">=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">chanStop1</span> <span style="color: white;">=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">bool</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="color: white;">a</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTimer(</span><span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">*</span> <span style="color: white;">time.Second)</span>
<span style="color: white;">b</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTimer(</span><span style="color: #0086f7; font-weight: bold;">10</span> <span style="color: white;">*</span> <span style="color: white;">time.Second)</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: white;">Runner1()</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">select</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;"><-a.C:</span>
<span style="color: white;">chanStop1</span> <span style="color: white;"><-</span> <span style="color: #fb660a; font-weight: bold;">true</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;">strMessage</span> <span style="color: white;">:=</span> <span style="color: white;"><-chanRunner1:</span>
<span style="color: white;">fmt.Println(strMessage)</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;">strMessage</span> <span style="color: white;">:=</span> <span style="color: white;"><-chanRunner2:</span>
<span style="color: white;">fmt.Println(strMessage)</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;"><-b.C:</span>
<span style="color: white;">fmt.Println(</span><span style="color: #0086d2;">"DONE!"</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: #fb660a; font-weight: bold;">default</span><span style="color: white;">:</span>
<span style="color: #fb660a; font-weight: bold;">continue</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">Runner1()</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: white;">Runner2()</span>
<span style="color: white;">c</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(</span><span style="color: #0086f7; font-weight: bold;">500</span> <span style="color: white;">*</span> <span style="color: white;">time.Millisecond)</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: white;"><-c.C</span>
<span style="color: #fb660a; font-weight: bold;">select</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;"><-chanStop1:</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: #fb660a; font-weight: bold;">default</span><span style="color: white;">:</span>
<span style="color: white;">chanRunner1</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Howdy from Runner1!"</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">Runner2()</span> <span style="color: white;">{</span>
<span style="color: white;">d</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(</span><span style="color: #0086f7; font-weight: bold;">500</span> <span style="color: white;">*</span> <span style="color: white;">time.Millisecond)</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: white;"><-d.C</span>
<span style="color: white;">chanRunner2</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Hello from Runner2!"</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</td></tr>
</tbody></table>
</div>
<br />
Like my previous "let's test a theory" test applications, this one is pretty straightforward. There are two functions; Runner2(), whose only job is to create a ticker that ticks every 500 milliseconds and when that tick fires it sends "Hello from Runner2!" to a channel called chanRunner2.<br />
<br />
Runner1() is just like Runner2(), except it first spawns Runner2() before it starts firing a slightly different message into a channel called chanRunner1 every 500 milliseconds. There is one other small addition; Runner1() listens to a channel called chanStop1 and if anything comes down the pipeline, it calls return.<br />
<br />
Then there's main(); main() creates two timers (not tickers), one that will fire in 5 seconds and one that will fire in 10 seconds. Main() then spawns Runner1() and starts a loop listening for either a timer to fire or a message from channels chanRunner1 or chanRunner2, with a default of "continue" so the select statement keeps re-evaluating in a loop.<br />
<br />
<span style="font-size: large;"><b>Expected Output:</b></span><br />
<br />
Because of the nature of goroutines and the tickers (not timers...there's an important difference...) the output should be "Howdy from Runner1!" interspersed with "Hello from Runner2!". After 5 seconds, the first timer fires, and Runner1() calls return; either both lines stop writing to the console because Runner1() returns and kills Runner2() with it, or "Hello from Runner2!" continues for the next 5 seconds without the other message interleaved, meaning that you can kill the routine that created another goroutine without having any effect on the "grandchild" goroutine to main().<br />
<br />
<span style="font-size: large;"><b>Actual Output:</b></span><br />
<br />
Drumroll, please...<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #f0f3f3; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;">./chained_goroutines
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Howdy from Runner1!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
Hello from Runner2!
DONE!
</pre>
</div>
<br />
There it is; Runner2() kept running after Runner1() exited. Something to keep in mind when modeling how your application works!Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0tag:blogger.com,1999:blog-2854270733196620893.post-57198089313948254932016-04-13T19:30:00.000-04:002016-04-17T14:38:04.337-04:00Athens School Board's Response to the 2016 Threatened Strike; Disingenuous AgainThe teacher's Union has finally...after over 3 years of negotiation stalemate...threatened a strike.<br />
<br />
The School Board responded pretty much as I thought they would; they quickly put up a rope and aired their version of the dirty laundry. And just as relevant, they blamed the teachers for making the britches dirty even though it's their crap on the backside.<br />
<br />
The Board posted, on the school website (which still pisses me off as the Union can't post information to the school website for their side of the story, but the School Board likes to post propaganda to the front page of the district site, making it harder for the public to actually piece together a coherent picture of what's going on if they are so inclined), their "<a href="https://docs.google.com/a/athensasd.k12.pa.us/viewer?a=v&pid=sites&srcid=YXRoZW5zYXNkLmsxMi5wYS51c3xhYXNkfGd4OjZiMjBkNjM5NDE3NmQ3ODU" target="_blank">Responsnse from the AASD school board.pdf</a>." (The typo is theirs; I thought it amusing, so I pointed it out, although they may change it later).<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4gZ3X_9vGpZWGPoRuw7aHKOiUHL53JQwN-iJZ4cSqkNGgPSfEXEn3nN5oojQDlrrfRRgMVNTJ_Ab-vBfu83-nA-i4kGJNhTTTlFGRXR39_m8IwRdwHRkGqaiovg4hnA-qF_y96-BldYs/s1600/Resposnse_from_the_AASD_school_board_pdf.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="249" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi4gZ3X_9vGpZWGPoRuw7aHKOiUHL53JQwN-iJZ4cSqkNGgPSfEXEn3nN5oojQDlrrfRRgMVNTJ_Ab-vBfu83-nA-i4kGJNhTTTlFGRXR39_m8IwRdwHRkGqaiovg4hnA-qF_y96-BldYs/s640/Resposnse_from_the_AASD_school_board_pdf.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">A werd frum ower sponser</td></tr>
</tbody></table>
<br />
They start off painting the teachers as evil, untrustworthy and shifty. In their response, the Board used phrasing such as, "We would like to add that at no time has the Athens Area School Board negotiation team members met at the table, unwilling to negotiate" to insinuate that they are the victims of a vicious Union (although phrasing is kind of important, since this sentence could be read as they simply never met at the table...). They say, "We have not put unrealistic timelines or demands on the AAEA, while that has not been the case by the AAEA." That's strange, given that the opening of the paragraph states they've had 3.5 years with no meaningful movement in negotiations.<br />
<br />
After setting up those dominoes, the Board points out with evident self-satisfaction that the Union reneged on their official statement from November 2015 that stated they would allow a deadline of four weeks after a state budget was passed for a reasonable settlement to be proposed.<br />
<br />
The state budget was officially allowed to lapse into passing on March 27th, and here we are with a threatened strike <u>STARTING ON APRIL 18th</u>! THOSE LYING UNION BASTARDS! They even ended the paragraph with an unsubstantiated claim that they offered to meet for negotiations but the AAEA "simply would not meet with us." In summary, "You can clearly tell we're victims of these horribly unreasonable jackbooted thugs." You can almost picture them cringing as the teachers march into the room in full uniform regalia, drooling in anticipation of crushing the kindhearted and well-intentioned School Board under their collective bargaining heels.<br />
<br />
And they're right! The Union did take a strike vote in less time than promised. However, the Board failed to emphasize that the ultimatum was for a <i>reasonable proposal</i> and <i>show an attempt to bargain in good faith.</i> That's really a weak ultimatum. It's like telling your kid they better not hit their sibling again or you'll maybe punish them. Reasonable proposal. Show an <i>attempt</i>. <a href="http://bsilverstrim.blogspot.com/2015/12/athens-pa-union-vs-school-board-i-think.html" target="_blank">And how did the Board react</a>?<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf8YP0wg-hBBbz9v33T6ev8bM6tVSpoqNZKoSSkrMM41J4A-3w3NQrp2I5bd-iBKvGXUZPrVVeM-RoefTwJaeuLnAkU32-loutygmxBXlxE8vMMPThoC0_Lq6C52Aj0o_Sn7IYOEyqcA8/s1600/war.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="225" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjf8YP0wg-hBBbz9v33T6ev8bM6tVSpoqNZKoSSkrMM41J4A-3w3NQrp2I5bd-iBKvGXUZPrVVeM-RoefTwJaeuLnAkU32-loutygmxBXlxE8vMMPThoC0_Lq6C52Aj0o_Sn7IYOEyqcA8/s400/war.jpg" width="400" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Yeah, seems reasonable</td></tr>
</tbody></table>
<br />
<div class="p1">
<div class="p1">
The Union stated publicly at a school board meeting on April 12th, when questioned about this decision, that the negotiation team felt the Board was refusing to further negotiate after their last session as a member of the School Board’s negotiation team declared, “We have nothing more to talk about.” At that point, the Union’s executive team made the decision to ask their members to vote on a strike. </div>
</div>
<br />
<div class="p1">
<span class="s1">The next bit of the Board’s response is an outline of goals to establish how reasonable the Board is. Three are pretty clear-cut. The last one is fluff, as there's absolutely no way to measure it, but makes them sound like they care about something important. Be allowed to hire the best professional staff members? After they stated goals that equate to trying to save money and lower pay, during a three and a half year standoff? You don't want the best. You want the most naive new recruits, too inexperienced to know that you're beating them in the head with a switch from a whackin' tree while you're telling them you're doing them a favor. Pro tip: don't start off highlighting why you're being browbeaten for 3+ years by an evil Union regime while bravely fighting to reduce your teacher's benefits and pay only to end it by saying you're trying to recruit the cream of the crop to work for you.</span></div>
<br />
<div class="p1">
<span class="s1">Next, they decide to roll into the biggest issue, the nearest and dearest to the taxpayer heart—teacher salaries. Teachers are too expensive to hire! The Board repeatedly presses to not give retroactive pay (after over three years of refusing to actually settle the contract, pretending that when this one is eventually passed they won't have to immediately settle the NEXT CONTRACT because they couldn't do their job...) and lower the pay increases due when teachers increase their experience/education levels. They do this by appealing to the public's basic grasp of math, because nuance is hard.</span></div>
<br />
<div class="p1">
<span class="s1">TEACHERS MAKE $66,000 A YEAR ON AVERAGE! THE AVERAGE BRADFORD COUNTY INCOME IS $48,000! HOW FAIR IS THAT?! They even published a table of teacher salaries; they were kind enough to omit the names, but it wasn't really much of a kindness. Teacher salaries are public knowledge. While the table they provide is semi-anonymized, the data has enough information to combine with the links to the (slightly out of date) data for public teacher salary records to figure out who is who, with a bonus of now knowing their employee identification numbers used in internal business records. So, yay for more "here's how you phish for data" handed out.</span></div>
<br />
<div class="p1">
<span class="s1">Holy shit. That sweet Bill Gates paycheck must be why the teachers be rollin' in to the parking lot with diamond-encrusted Lambo's and gold-trimmed Porsches. Sounds pretty bad. Why do they get so much when I don't!? The Board doesn't link to the document from which they pulled the numbers, but mention it's from the US census.</span></div>
<br />
They weren't lying, but they weren't entirely truthful. <a href="http://www.census.gov/quickfacts/table/INC110214/00,42015,4201503400" target="_blank">Here's a handy link to the census information at Census.Gov.</a> It's kind of weird that the Bradford County household income is $48,000, but the US average is $53,000 and Athens Township has an average income of $51,700. But I guess the $48,000 statistic paints a more outrage-inducing picture.<br />
<br />
<div class="p1">
<span class="s1">But is that the whole picture? Probably not, considering that income tends to be tied to education level. Teachers are required to have ongoing education credits. Basically the government tells them they need continued schooling or some equivalent (One of the step items the Board wants to not pay them extra for having attained) in order to retain certification. Nearly 88% of Bradford County aged 25+ (and 90% of Athens Township) have high school degrees or higher. But only 17% of Bradford County (and 23% of Athens) has a bachelor's degree or higher! </span></div>
<br />
And in Athens, the major industries are...hospitals...the school...and...what? Most of your business booms are fast food, Wal-Mart and new hotels. At least, those are the <i>visible</i> new jobs. <a href="http://www.city-data.com/city/Athens-Pennsylvania.html" target="_blank">City-data.com says Athens' most common industry is manufacturing (27%) and the most common occupations are production- and construction- related (15% and 12%, respectively.)</a> Knowledge workers with higher degrees seem to leave the area.<br />
<br />
<div class="p1">
<span class="s1">But of the educated, what are their average incomes? The Board is comparing a large population of mostly non-degreed members with teachers, who not only have at least a Bachelor's degree, but are required to continue with education in a rather specialized niche. It's not uncommon for teachers to end up with master’s degrees or higher. They're almost forced to.</span></div>
<br />
<a href="http://www.bls.gov/emp/ep_chart_001.htm" target="_blank">The Bureau of Labor Statistics says the median weekly earnings of a person with a Bachelor's degree (2015) is $1,137</a>. There's 52 weeks a year, so that comes to a little over $59,000/year. Strange...that's not too far from the teacher's salaries. Master's degrees earn about $1,341/week, or $69,732/year.<br />
<br />
That means the teacher income average in Athens, at $66,000/year, is well within the "average" mark. A high school diploma average is $678/week or $35,256/year, for what it's worth.<br />
<br />
I suppose the board would say that the $66,000 is still significantly higher than the average for having a Bachelor's degree. Let's take a quick glance at the data in the tables they published online to illustrate how overpaid their semi-anonymized teachers are.<br />
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTqpXR4OMjd4VnLNOdft_SrRZJZ6mNEdZjpP0tlRxHpy8Ir6l8rx6onu4SYXHvb4Nymq_iD8bForjifxuTTErLdlGuxr56nfKFYRc2xttNJiYF4luQ-kyadq2IBRXt9IQqt6dGn4XoSJI/s1600/Staff_Hiring.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="374" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhTqpXR4OMjd4VnLNOdft_SrRZJZ6mNEdZjpP0tlRxHpy8Ir6l8rx6onu4SYXHvb4Nymq_iD8bForjifxuTTErLdlGuxr56nfKFYRc2xttNJiYF4luQ-kyadq2IBRXt9IQqt6dGn4XoSJI/s640/Staff_Hiring.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Interesting how the median looks like a middle finger</td></tr>
</tbody></table>
Most of the staff were hired between 6 and 18 years ago! I count 27 people hired before 2000, 52 employees were hired between 2000 and 2010, and 18 from 2010 to 2015. Four of the 27 were hired in the 80's! There's a significant number of experienced staff! (Note I figured this up by hand from <a href="https://docs.google.com/a/athensasd.k12.pa.us/viewer?a=v&pid=sites&srcid=YXRoZW5zYXNkLmsxMi5wYS51c3xhYXNkfGd4OjcwYTIzNzg3Njc3ZWI2ZTU" target="_blank">the chart the board provided</a>. I may have a miscount. Feel free to double check, let me know if I missed something in the comments...)<br />
<br />
<div class="p1">
<span class="s1">Remember that bit the Board claimed about wanting to hire the best? Unless your job makes you bitter you usually get better at your job with additional experience.</span></div>
<br />
<div class="p1">
<span class="s1">Unfortunately that's the garlic to the Board's vampire. These are educated professionals; a significant number of them have decent experience, and have been getting continuing education. That means they're going to be closer to the upper pay limit both because they've been there a while AND they have paper saying they're smarter.</span></div>
<br />
<div class="p1">
<span class="s1">In other words, you're paying for people who are better. And the Board doesn't want to pay them. I wouldn't be surprised if part of that spike in 2013 through 2015 is comprised of inexperienced graduates...they're cheaper.</span></div>
<div class="p2">
<span class="s2"></span><br /></div>
<i>And that's what the board is focused on. Teachers are expensive. Cut them down at all costs. </i><br />
<i><br /></i>
<br />
<div class="p1">
<span class="s1">The Board’s information about the teacher work day is also misleading. Teachers are obligated to work 7.5 hours a day with a half hour duty free lunch (SLACKERS!)</span></div>
<br />
<div class="p1">
<span class="s1">The language in their response is kind of funny. "Only work a 7.5 hour day." The 40-hour workweek, when I last checked, was comprised of 5 8-hour days. Even McJobs are required by law to give you 30-minute lunches when you work over five hours in a single shift. Plus breaks. The board is complaining that the teachers aren't obligated to work the hours of </span>an hourly fast food worker.</div>
<br />
<div class="p1">
<span class="s1">Well, not quite. The board goes on to complain that in addition to the 2.5 hours of lunchtime they get a week, teachers are allowed 3.75 hours per week of self-directed time. THEY WERE EVEN ALLOWED TO GO TO WALMART OR THE BANK. It's like the inmates are running the asylum. I'm pretty sure at one point the Board proposed adding instructional time by having staff wear diapers to save trips to the bathroom. (That 3.75 hours was roughly 45 minutes a day. According to most teachers, this time was usually used to correct papers and prepare for another class period. They weren't watching Netflix or leaving en masse to get more adult diapers from Walmart every day, although I can see why the Board would be horrified that teachers would run an errand while stores were open.)</span></div>
<div class="p2">
<span class="s2"></span><br /></div>
To further illustrate how unreasonable teachers are, the Board said they REFUSED to work an additional 15 minutes a day without being compensated. What they still refuse to acknowledge is that teachers already work this additional time. They're just not contractually obligated to do so during the school day. The Washington Post reported that <a href="https://www.washingtonpost.com/blogs/answer-sheet/post/survey-teachers-work-53-hours-per-week-on-average/2012/03/16/gIQAqGxYGS_blog.html" target="_blank">the Bill & Melinda Gates Foundation along with Scholastic issued a report showing that the average teacher works 10 hours and 40 minutes a day</a>. Yes...obligated to work 37.5 hours a week, but actually on average, working 53 hours a week.<br />
<br />
From their web page:<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">The 7.5 hours in the classroom are just the starting point. On average, teachers are at school an additional 90 minutes beyond the school day for mentoring, providing after-school help for students, attending staff meetings and collaborating with peers. Teachers then spend another 95 minutes at home grading, preparing classroom activities, and doing other job-related tasks. The workday is even longer for teachers who advise extracurricular clubs and coach sports —11 hours and 20 minutes, on average. As one Kentucky teacher surveyed put it, “Our work is never done. We take grading home, stay late, answer phone calls constantly, and lay awake thinking about how to change things to meet student needs.”</span><br />
<br />
<div class="p1">
To my knowledge, the Board has never acknowledged this. In fact, they have previously attacked teachers for being overpaid while using only the contractually obligated work time as their measure. This extra time is no secret among teachers, and it's nothing new. Just doing the basic math for hand-grading a two-page report for 30 kids in one class can take a significant chunk of time from an English teacher; if you assume 5 minutes per report (which is an unrealistic deadline to begin with), it’s 150 minutes, or 2 hours and 30 minutes. For one class. What does the Board think is going on during that "self directed" time? Teacher disco hour?</div>
<br />
<div class="p1">
It's at a point where the Board would have to be purposely playing stupid to not know how this factors in. The requirements put on teachers, with homework correction load and prep, makes accomplishing what needs to be complete within the time allotted a joke. It's as if you were tasked with moving a giant mound of sand from point A to point B; you are going to be paid to do it in an hour. The job has to get done, or you're fired and not paid for the job. But you will only be paid for an hour of work...even though it takes two hours to accomplish. Yet, teachers still accept this as part of the burden of working as a teacher even as the Board makes it abundantly clear that they're either clueless about what it takes to teach, or they simply enjoy making the work environment miserable.</div>
<br />
<div class="p1">
It’s laughable that the Board insists they "...understand the importance of its professional staff to remain lifelong learners" before moving on to propose eliminating some salary benefits to continuing education along with a "if you leave within 4 years of tuition reimbursement you have to pay it back" and a cap on reimbursement spending. Does the Board not understand what they're saying there? "We know this is important. So we'd like to limit it in many ways, along with adding financial uncertainty by forcing you to pay back the education you're required to get if something happens where you leave our employ AND only some teachers can continue their education at a time.” How do you negotiate with this kind of cognitive dissonance?</div>
<br />
<div class="p1">
Then they start winding down with some mini-zings, the bits that don't all seem to make much sense as points of contention unless they are actually put into context. Eliminating the transfer clause that allows seniority to be factored into filling vacant positions? What's the problem there? Not much, except it pretty much is meant to allow administrators and the Board to place favorite hires into new positions and add pressure to get rid of the expensive experienced teachers, assisting in eliminating positions by attrition (see the number of recent hires? Just speculating...)</div>
<br />
<div class="p1">
Most of the Board's response (or "resposnse", which still makes me giggle) is disingenuous at best. They even claim, "There have been 2 independent fact finder reports completed in the last year. Both reports were rejected by the AAEA." Those unreasonable Union bastards!</div>
<br />
<table align="center" cellpadding="0" cellspacing="0" class="tr-caption-container" style="margin-left: auto; margin-right: auto; text-align: center;"><tbody>
<tr><td style="text-align: center;"><a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX9l9e9jIC4aa2t3u4AHTRoxI9_8OFZhOWvo8O8tXzm3s4vZ-O7g28-3TFot6Ew396R3l4xaOXaJrJdEKZgSCA4bba7jUblRDtndzPgjiDF2ozA_Jzc8OxSB4pyO4U2lEfHb-H7PPpQt0/s1600/reject.jpg" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="258" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjX9l9e9jIC4aa2t3u4AHTRoxI9_8OFZhOWvo8O8tXzm3s4vZ-O7g28-3TFot6Ew396R3l4xaOXaJrJdEKZgSCA4bba7jUblRDtndzPgjiDF2ozA_Jzc8OxSB4pyO4U2lEfHb-H7PPpQt0/s640/reject.jpg" width="640" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">Those unreasonable Union bastards...wait, what the hell?</td></tr>
</tbody></table>
<br />
Um...that's kind of awkward. It pretty clearly says that the fact finder report was yet to be voted on by the Union for acceptance <a href="http://www.thedailyreview.com/news/2015-10-08/Today's_Top_Stories/Athens_School_Board_rejects_fact_finder_report_awa.html" target="_blank">when the Board already rejected it.</a> October 8 of 2015. Unanimously. <a href="http://www.dli.pa.gov/Individuals/Labor-Management-Relations/plrb/fact-finding/Pages/default.aspx#.Vw7TmJMrIUG" target="_blank">It's available on the labor relations board website, by the way.</a><br />
<br />
But if you were to just read the Board response, the rejection was all on the Union. Funny how a simple Google search shows that insinuation is utter crap.<br />
<br />
<a href="http://www.pressconnects.com/story/news/education/2014/06/17/athens-teachers-authorize-fall-strike-over-stalled-contract-talks/10678027/" target="_blank">The next page had a listing for an article from June 2014 when the board once again rejected a fact finder's report</a> (and this one pointed out the teachers accepted the report.)<br />
<br />
And for all the calls for saving money, the Board seems oddly bent on wasting money in other areas. For example,<a href="http://www.thedailyreview.com/news/2016-02-11/Today%27s_Top_Stories/Transportation_audit_shows_problems_in_Athens_SD.html" target="_blank"> they recently spent $15,000 on a study that told them they were wasting $800,000 on transportation.</a> There's bound to be some variability in spending...but $800,000? It's kind of an amazing article to read. <a href="https://drive.google.com/a/stackoverflow.com/file/d/0B2az96SPRH0IWEoyR2NqT1ZUN28/view" target="_blank">And the report, too</a>.<br />
<br />
The Board is also cutting two checks to lawyers. <a href="https://drive.google.com/a/stackoverflow.com/file/d/0B2az96SPRH0IV1BSZnU4TWp5aTQ/view" target="_blank">This has a bill from John Audi</a> (Sweet, Katz, & Williams) in January for nearly $6,000. (also one to a doctor, <a href="http://www.healthgrades.com/physician/dr-sidney-ranck-w63x9" target="_blank">Sidney G. Ranck, Jr.</a>, for $1,200...he's in obstetrics and gynecology. That's kind of...disturbing?)<br />
<br />
<a href="https://drive.google.com/a/stackoverflow.com/file/d/0B2az96SPRH0IWUFxWXQyRFdfTFU/view" target="_blank">This bill has John Audi getting a check cut for nearly $3,000</a>. <a href="https://drive.google.com/a/stackoverflow.com/file/d/0B2az96SPRH0IQWpNSV9tcEMtVkk/view" target="_blank">And this one is another $6,500 check, along with their second lawyer, Pat Barrett, getting a check for $6,000</a>. The list goes on.<br />
<br />
<a href="http://www.pressreader.com/usa/the-daily-review/20150510/281539404526330/TextView" target="_blank">And this is in addition to the <b>acting</b> superintendent's $130,000 salary</a> (strange that seems to be missing from the salary list the Board is holding up as evidence that teachers are overpaid...)<br />
<br />
The interesting part of that salary is that the previous superintendent was getting about $122,000/year. The <b>acting</b> superintendent isn't qualified to be superintendent and he's getting paid more. Part of me wonders if it's a gender thing...but that would be speculation. He's literally not qualified. The Board is paying for him to take classes and get his certification. That's why he's an <b>acting</b> superintendent. The previous superintendent was hired away from another district and had several years of experience. The new one is making more money and doesn't have a certificate. Somehow the Board equates this with hiring the best staff as per their resolutions back in January of 2013.<br />
<br />
Overall the whole "response," in my opinion, is one long exercise in misleading the public. Take the claims that they have been open to bargaining this whole time in good faith with a grain of salt. They claim to care about the community and educating the kids, but their actions demonstrate, quite loudly, otherwise.<br />
<br />
<span style="font-size: large;">ADDENDUM</span><br />
<br />
I did some quick checking of how much the frugal school board is spending on lawyer's fees. These figures were taken by eyeballing the board bills found on the <a href="http://www.athensasd.k12.pa.us/district/administration/athens-area-school-district-board/meeting-agendas" target="_blank">school website</a>. These reports are in PDF format, making them really really difficult to process in an automated fashion. Since I couldn't process them automatically I may have missed some payments, so the numbers I have, assuming I didn't misread some line items, would be considered a <b>minimum</b> paid to <b>two law firms </b>over the past 3 years, meaning there are probably payments missing. I think the contract talks may have extended beyond what bill items are on the website.<br />
<br />
Regardless, this kind of money is interesting given how much the Board speaks of money problems and how expensive teachers are. It's also interesting how much vested interest the legal counsel has in prolonging the contract talks. How many meetings are there for negotiations? How much are they making per meeting?<br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlz6ghR4VSoxjzgZKQFegh0tyPWwjuTbUnJ_yZmOoFXlhFvJIQGOYbF8udqPXNHgP4iqDinY3MESS7iP9gzE_HBykClcGlF2MHOoEBys-2aEvbul1niQLF3uA3aKnorQc_jeujcPLBVwc/s1600/legal.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="566" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjlz6ghR4VSoxjzgZKQFegh0tyPWwjuTbUnJ_yZmOoFXlhFvJIQGOYbF8udqPXNHgP4iqDinY3MESS7iP9gzE_HBykClcGlF2MHOoEBys-2aEvbul1niQLF3uA3aKnorQc_jeujcPLBVwc/s640/legal.jpg" width="640" /></a></div>
<br />
The numbers are all there, listed with dates the checks were cut. Feel free to double check my numbers and tell me if I'm missing something. Also, I'm aware that the solicitor for the board (P.B.) does other duties, so these are not funds spent only on fighting the Union; I'm not privy to the other duties, however, so I can't break down the numbers into sub-categories. I've been told the John Audi firm was hired just to fight the Union, however, so big numbers are still big numbers.<br />
<span style="font-size: large;"><br /></span>
<span style="font-size: large;">Update 4-17-16</span><br />
<br />
One of the sticking points in contract negotiations is in regards to the number of consecutive personal days a teacher may take. But it strikes me as being rather odd...what do they hope to accomplish by limiting consecutive personal days when teachers only get 3 personal days per year?<br />
<br />
<a href="https://www.facebook.com/AthensAreaEA/photos/pcb.1699125980375036/1699125953708372/?type=3&theater" target="_blank">The AAEA (Union) provided an answer with a FaceBook post.</a><br />
<br />
<div class="separator" style="clear: both; text-align: center;">
<a href="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4azoCW6V92DE8i8Gd4824cRZAxBBqc5peC76KjDHd20dfCnNH9tGvxWT6Zf8qh2H72C7TXyNtyr2vLEyelm4QZApoqg4ll3e_ktZUPtaOq8dUzJcfAG0xEqua4LkQ1oswilAmMk_D9sQ/s1600/personal_days.jpg" imageanchor="1" style="margin-left: 1em; margin-right: 1em;"><img border="0" height="396" src="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEj4azoCW6V92DE8i8Gd4824cRZAxBBqc5peC76KjDHd20dfCnNH9tGvxWT6Zf8qh2H72C7TXyNtyr2vLEyelm4QZApoqg4ll3e_ktZUPtaOq8dUzJcfAG0xEqua4LkQ1oswilAmMk_D9sQ/s640/personal_days.jpg" width="640" /></a></div>
<br />
A Board member wanted to "talk" (usually situations like this implies "complain", but given a lack of specific information, that again is speculation) with a teacher. Teacher was on vacation. School board member now just happens to be pushing for limits on teacher time off.<br />
<br />
If the implication is true the push to limit personal time off is purely for personal reasons, not for the benefit of the community taxpayers. This is a vendetta as a negotiations sticking point. How many other points of negotiation are driven by purely personal reasons?<br />
<br />Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com4tag:blogger.com,1999:blog-2854270733196620893.post-5970292705912100792016-04-11T21:33:00.000-04:002016-04-11T21:33:04.559-04:00GoLang: More on Mutexes (A Followup)In my previous post I explored a little bit with sync.Mutex behavior in locking changes to a struct. I was mainly focusing on how to model the behavior in my head when trying to implement a way of locking a struct for alterations to protect it from having other goroutines alter it in the middle of an operation (which after exploring mutex behavior I discovered that it doesn't really lock the struct at all, even though it kind of nearly sort of works like that when modeling the workflow I needed.)<br />
<br />
The fact was that the mutex acts more like a flag, and encapsulating code in a lock()/unlock() bound to a particular mutex variable seemed to be a way of isolating code from running at the same time (struct or no struct.)<br />
<br />
Is that a more proper way to think about mutexes in Go? Let's try a simple application to find out.<br />
<!-- HTML generated using hilite.me --><br />
<div style="background: #111111; border-width: 0.1em 0.1em 0.1em 0.8em; border: solid gray; overflow: auto; padding: 0.2em 0.6em; width: auto;">
<pre style="line-height: 125%; margin: 0;"><span style="background-color: #0f140f; color: #008800; font-style: italic;">// More testing on Mutex use</span>
<span style="color: #fb660a; font-weight: bold;">package</span> <span style="color: white;">main</span>
<span style="color: #fb660a; font-weight: bold;">import</span> <span style="color: white;">(</span>
<span style="color: #0086d2;">"fmt"</span>
<span style="color: #0086d2;">"sync"</span>
<span style="color: #0086d2;">"time"</span>
<span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">func</span> <span style="color: white;">main()</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// The mutex to lock</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">MyLock</span> <span style="color: white;">sync.Mutex</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Save the current time</span>
<span style="color: white;">StartTime</span> <span style="color: white;">:=</span> <span style="color: white;">time.Now()</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Create a channel to organize text messages to the user</span>
<span style="color: white;">chanText</span> <span style="color: white;">:=</span> <span style="color: white;">make(</span><span style="color: #fb660a; font-weight: bold;">chan</span> <span style="color: #cdcaa9; font-weight: bold;">string</span><span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Simple counter for formatting purposes</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">boolNewline</span> <span style="color: #cdcaa9; font-weight: bold;">bool</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">false</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// For the next 5 seconds, output a period every tick</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"For ~5 seconds, Process 1 is printing a '.' every 100 milliseconds!"</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Set a simple flag</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">TriggerMessage</span> <span style="color: #cdcaa9; font-weight: bold;">bool</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">true</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// A ticker</span>
<span style="color: white;">ticker</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(time.Millisecond</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Output something with each tick</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: #fb660a; font-weight: bold;">range</span> <span style="color: white;">ticker.C</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Check the time since the program started</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// It was <= 5 seconds. Print the .</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"."</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;">>=</span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">&&</span> <span style="color: white;">TriggerMessage</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// A simple notification</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"For the next ~5 seconds, the lock is going to be set for Process 1!"</span>
<span style="color: white;">TriggerMessage</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">false</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;">></span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">&&</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">10</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Now it's time for the locked portion of the demo</span>
<span style="color: white;">MyLock.Lock()</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Ticker within a ticker</span>
<span style="color: white;">ticker2</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(time.Millisecond</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">)</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// What follows is a label, OutOfTicker, which will be used for a nested break statement</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// to escape the embedded ticker/if statement</span>
<span style="color: white;">OutOfTicker:</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: #fb660a; font-weight: bold;">range</span> <span style="color: white;">ticker2.C</span> <span style="color: white;">{</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Run until the final runtime total (of 10 seconds)</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">10</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"."</span><span style="color: white;">)</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">break</span> <span style="color: white;">OutOfTicker</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Unlock and notify the user</span>
<span style="color: white;">MyLock.Unlock()</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Process 1 unlocked mutex!"</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}()</span>
<span style="color: #fb660a; font-weight: bold;">go</span> <span style="color: #fb660a; font-weight: bold;">func</span><span style="color: white;">()</span> <span style="color: white;">{</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"For ~5 seconds, Process 2 is printing a '*' every 100 milliseconds!"</span>
<span style="color: #fb660a; font-weight: bold;">var</span> <span style="color: white;">TriggerMessage</span> <span style="color: #cdcaa9; font-weight: bold;">bool</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">true</span>
<span style="color: white;">ticker</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(time.Millisecond</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: #fb660a; font-weight: bold;">range</span> <span style="color: white;">ticker.C</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"*"</span><span style="color: white;">)</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;">>=</span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">&&</span> <span style="color: white;">TriggerMessage</span> <span style="color: white;">{</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"For the next ~5 seconds, the lock is going to be set for Process 2!"</span>
<span style="color: white;">TriggerMessage</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">false</span>
<span style="color: white;">}</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;">></span> <span style="color: #0086f7; font-weight: bold;">5</span> <span style="color: white;">&&</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">10</span> <span style="color: white;">{</span>
<span style="color: white;">MyLock.Lock()</span>
<span style="color: white;">ticker2</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTicker(time.Millisecond</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">100</span><span style="color: white;">)</span>
<span style="color: white;">OutOfTicker:</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: #fb660a; font-weight: bold;">range</span> <span style="color: white;">ticker2.C</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">time.Since(StartTime).Seconds()</span> <span style="color: white;"><=</span> <span style="color: #0086f7; font-weight: bold;">10</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"*"</span><span style="color: white;">)</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">break</span> <span style="color: white;">OutOfTicker</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">MyLock.Unlock()</span>
<span style="color: white;">chanText</span> <span style="color: white;"><-</span> <span style="color: #0086d2;">"Process 2 unlocked mutex!"</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}()</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// And now we kill main() after a set period of time, which is 2 seconds after everything has</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// hopefully finished without issue</span>
<span style="color: white;">timer</span> <span style="color: white;">:=</span> <span style="color: white;">time.NewTimer(time.Second</span> <span style="color: white;">*</span> <span style="color: #0086f7; font-weight: bold;">12</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">for</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">select</span> <span style="color: white;">{</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;"><-timer.C:</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Time to stop</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Send a formatting newline and exit main()</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"\n"</span><span style="color: white;">)</span>
<span style="color: #fb660a; font-weight: bold;">return</span>
<span style="color: #fb660a; font-weight: bold;">case</span> <span style="color: white;">strMessage</span> <span style="color: white;">:=</span> <span style="color: white;"><-chanText:</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// Message from the goroutines</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// boolNewLine is checked so every other line is fed a newline, just to make the text</span>
<span style="background-color: #0f140f; color: #008800; font-style: italic;">// to the user more readable/neat, then flips the state of boolNewLine for the next iteration</span>
<span style="color: #fb660a; font-weight: bold;">if</span> <span style="color: white;">!boolNewline</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Print(</span><span style="color: #0086d2;">"\n"</span><span style="color: white;">)</span>
<span style="color: white;">fmt.Println(strMessage)</span>
<span style="color: white;">boolNewline</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">true</span>
<span style="color: white;">}</span> <span style="color: #fb660a; font-weight: bold;">else</span> <span style="color: white;">{</span>
<span style="color: white;">fmt.Println(strMessage)</span>
<span style="color: white;">boolNewline</span> <span style="color: white;">=</span> <span style="color: #fb660a; font-weight: bold;">false</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
<span style="color: white;">}</span>
</pre>
</div>
<br />
I think the code is pretty straightforward; the two goroutines are virtually identical, and the first one is heavily commented. Main() starts off with a declaration of a mutex called MyLock, then grabs the current time (as a time.Time type) stored as StartTime, since the application is going to run for X number of seconds from the start of execution.<br />
<br />
For the sake of simplifying formatting, I opted to create a channel for the two goroutines to send information back to main() and let main() be responsible for serializing and formatting the text. Because goroutines output is indeterministic, controlled by the scheduler, letting them dump text to fmt.Print() can be less predictable and a little less readable. Channels kind of filter the information; text is sent into the channel, they will be kind of serialized until the reader on the other side (in main()) pulls the message off the channel and processes it. One thing to note - you can affect the indeterministic nature of goroutine execution by not reading from the channel in main(); since it's a blocking operation (unless you use a channel with buffers), if you were to do something like time.Sleep() in main() where it should read more from the channel, you'll stop the goroutines from working when they try to send something down the channel again.<br />
<br />
The last thing I set up was a small boolean variable to again help with some formatting of text.<br />
<br />
After the spawning of the goroutines, main() declares a timer that will run for 12 seconds because the demo from the goroutines should finish in 10. After that is the loop that checks for either the timer to tick ("We're done!") or a message from the channel. The message flips the boolean between true and false because if I didn't, some of the text would get tacked on the end of the line of "." or "*" from the goroutines and it made it look bad. Because there's two goroutines, it was fairly simple to use the boolean to say "add a newline before printing this" and flipping the state so the next message was just, "Print the message."<br />
<br />
The goroutines are fairly simple too; most of their logic is a matter of evaluating time so they knew when to lock and unlock. They notify the channel that they're going to run for 5 seconds and create a boolean flag for their change in message later, then create a periodic 100ms ticker.<br />
<br />
Then it's time to listen to the ticker. If the time is less than 5 seconds since launch, it just sends a character to the console.<br />
<br />
If the time is greater than 5 seconds and my boolean flag is set, it sends the message that for the next 5 seconds it's going to set the lock, then turns off that message from reappearing using the boolean flag since otherwise it may keep printing it every 100 milliseconds, or once the later logic is through it might occasionally spill over again.<br />
<br />
(At this point I could probably have turned off the ticker within the next inner loop so that wouldn't be re-evaluated or set some conditional that would have invalidated it in later runs. I used the flag because it was relatively simple while I iterated through my tests for the blog post. Point is, there's more than one way to have achieved this, probably every way has some merit, but for this purpose it worked.)<br />
<br />
The next bit runs if the time is between 5 and 10 seconds. The goroutines set the lock and create a new timer, then I use a label (OutOfTicker) above an "inner evaluation" loop for the new timer. That small inner lop runs until 10 seconds since the launch of the program, then breaks out of the inner loops to the scope of the label. At that point they'll unlock the mutex and send a message down the channel that the mutex is unlocked.<br />
<br />
What does the output look like?<br />
<br />
<span style="font-family: "courier new" , "courier" , monospace;">go build && ./mutex_testering2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">For ~5 seconds, Process 1 is printing a '.' every 100 milliseconds!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For ~5 seconds, Process 2 is printing a '*' every 100 milliseconds!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">.*.*.*.*.*.**..*.**.*..*.*.**..**.*..*.*.*.*.*.*.**.*.*.*..**..**..**..**.*..**..**..*.*.*.*.*.*.*</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For the next ~5 seconds, the lock is going to be set for Process 1!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For the next ~5 seconds, the lock is going to be set for Process 2!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">.................................................</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Process 1 unlocked mutex!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Process 2 unlocked mutex!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">go build && ./mutex_testering2</span><br />
<span style="font-family: "courier new" , "courier" , monospace;"><br /></span>
<span style="font-family: "courier new" , "courier" , monospace;">For ~5 seconds, Process 1 is printing a '.' every 100 milliseconds!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For ~5 seconds, Process 2 is printing a '*' every 100 milliseconds!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">.*.*.*.**..*.*.*.*.**..**..*.*.**.*..**.*.*..**..**.*.*..*.**..**.*..**..*.*.*.*.*.*.**.*..**.*..*</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For the next ~5 seconds, the lock is going to be set for Process 2!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">For the next ~5 seconds, the lock is going to be set for Process 1!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">*************************************************</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Process 2 unlocked mutex!</span><br />
<span style="font-family: "courier new" , "courier" , monospace;">Process 1 unlocked mutex!</span><br />
<br />
I included two runs to show that the scheduling was random enough that sometimes the first goroutine has the lock and runs, and sometimes the second one gets to run with the lock. Because they're time restricted, they stop trying to run around the same time, so there's no blip from the opposite goroutine when the mutexes are released. Although it might be possible? Maybe?<br />
<br />
<span style="font-size: large;">Conclusion</span><br />
<br />
Thinking of mutexes as a way to lock a struct worked for my particular purpose when trying to imagine the workflow in my particular application at the time, but probably better to think of mutexes as a way of having your code check a variable to see if it's okay to run anything contained between the Lock() and Unlock(). The goroutines here were independently running, and once the mutex was locked only one of them was allowed to work; kind of like, "Whoever has the speaking stick may address the group" around a campfire. Mutexes are the speaking stick of Go applications!Bart Silverstrimhttp://www.blogger.com/profile/03331335618282966548noreply@blogger.com0