Thursday, July 13, 2017

Your Experiences Create Your Methods

Sounds obvious, doesn't it?

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.

(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.)

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.

The file was tens of thousands of lines; the program expected the format:
12345,string of text,state_name

The file we got was formatted:
12345,"string of text",state_abbreviation

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.

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.

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.

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.

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

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.

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

In the end it was the (timely) results that mattered.

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.

Wednesday, May 31, 2017

Programming a Stargate

I'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.

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.

"What does that have to do with Stargates?"

I really like Stargate. SG-1, Atlantis, or Discovery, doesn't matter (except the animated series...I pretend that doesn't exist.)

Some people hate it when geeks watch movies and get nitpicky about details. "CAN'T YOU JUST ENJOY THE MOVIE?!"

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

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.

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.

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

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

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.

Another mini-utility I worked on was checking validity of town locations. Pray you never have to work with FIPS...

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.

I'm sure that if I had a dial-home device (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.

While frustrating at times, I am thankful that these mini-projects have taught me a few things.

  • 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.
  • I end up rethinking a lot of parsing logic when digging and sorting through human language.
  • Web sites implement some seemingly convoluted logic for interacting with clients and I now have a new appreciation for web browsers.
  • 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.

Saturday, April 29, 2017

Learning By Creating Support Applications

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

(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...)

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.

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.

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.

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.

(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.)

How can a new hire get a grasp on this situation?

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.

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.

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.

Others are querying API endpoints for replies of what the services believe are their current health states.

Some queries are pulling the status of database indexing.

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.

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.

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.

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.

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!

Thursday, March 23, 2017

Golang: Remember This When Using Select For Monitoring Channels

I thought I would share something that's easy to overlook when using a loop to listen for messages from channels in Go.

The following is a simple code snippet:

for {
    for a := range structOfChannels {
        select {
        case msg := <- structOfChannels[a].Chan1:
            // Something
        case msg := <- structofChannels[a].Chan2:
            // Something
        default:
        }
    }
}

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.

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.

The solution was simple; make the loop wait a fraction of a second each iteration.

for {

    tmTimer := time.NewTimer(time.Millisecond * 50)
    <-tmTimer.C

    for a := range structOfChannels {
        select {
        case msg := <- structOfChannels[a].Chan1:
            // Something
        case msg := <- structofChannels[a].Chan2:
            // Something
        default:
        }
    }
}

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. 

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.

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!

Monday, March 6, 2017

When To Use "+" And When To Use "%20": Adventures In URL Encoding

I'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.

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.

I jest, but really the trouble was a matter of "it works if you know specifically how to make it work for this case."

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.

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

req, err := http.NewRequest("GET", strURL+"?q="+strQuery, nil)

...which promptly spit back a syntax error. Huh?

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.

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 bug report for Go. 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.

A more elaborate answer was found on Stack Overflow. 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 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.

In my case, Go liked the + for escaping strings and eschewed the percent-20. My fix? Right after running the url.QueryEscape():

strQuery = strings.Replace(strQuery, "+", "%20", -1)

Not the most elegant, but when I submitted that strQuery, the Java application was happy!

My takeaways:
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.
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.
3) Given the confusion of + versus %20 when searching around online, I'm not the only one having this kind of issue.
4) Just use %20. Unless I run into a specific case where the other side isn't translating %20 correctly.

Wednesday, December 7, 2016

Creating a Test Ensemble with ZooKeeper and VirtualBox

What is ZooKeeper? 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.

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.

That's the simplest overview.

I created 3 ZooKeeper nodes using VirtualBox.

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.

While running through the install I added the SSH server package when prompted.

Once the VM was running I ran
sudo apt-get update
sudo apt-get upgrade

I also ended up running
sudo apt-get dist-upgrade

At that point it no longer had packages to update nor packages held back.

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.

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.

Now I have 3 small servers running. In each of them, I ran
sudo apt-get install zookeeper

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):
server.1=192.168.254.1:2888:3888
server.2=192.168.254.2:2888:3888
server.3=192.168.254.3:2888:3888

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.

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:
sudo apt-get install zookeeperd

At this point I can run
sudo service zookeeper restart
sudo service zookeeper status

A basic ensemble (or cluster) should now be running!

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.
export JAVA=java

Now you can run the tool. This will launch it, and connect to a local server instance.
/usr/share/zookeeper/bin/zkCli.sh -server 127.0.0.1:2181

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:
ls /
create /zk_test My_Data
ls /
get /zk_test
set /zk_test test
get /zk_test
delete /zk_test
ls /
quit

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.

Thursday, November 10, 2016

Time Machine With File Vault Corruption: Reformatting the External Drive

I'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 really cranky when that happens.

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.

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

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.

But there is a way around it.

Use this command to identify your external drive (the Time Machine drive, in my case)

diskutil list

Su to root.

sudo su

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

cat /dev/random > /dev/disk1

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.

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.

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.

Tuesday, October 25, 2016

Apple Remote Desktop (ARD) Can't Find Machines

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

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

Summary: Remove cached settings from ARD, remove network DNS/ARP caches on machine, kick ARD in the head...

  1. In ARD, go to the All Computers list, highlight the machine names and delete them.
  2. Quit ARD.
  3. Flush DNS cache: sudo dscacheutil -flushcache;sudo killall -HUP mDNSResponder
  4. Flush ARP: sudo arp -ad
  5. Kick ARD in the head by restarting the ARD agent (on clients): /System/Library/CoreServices/RemoteManagement/ARDAgent.app/Contents/Resources/kickstart -restart -agent

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.

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.

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.

Whoops.

Wednesday, September 21, 2016

Skittles are to Refugees what M&Ms are to Not All Men

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

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.

Donald Trump, Jr. used the recent bombings in Chelsea and New Jersey to compare refugees to a bowl of perhaps-poisoned candy. Basically, the argument goes, if you have a bowl of Skittles and 3 of them were poisoned, would you take a handful?

The makers of Skittles were not amused, as you can imagine. Their reply 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...)

Leaving aside the argument that the suspect in the bombing is a naturalized American citizen or that the actual math behind your odds of dying at the hands of terrorism in the US are minuscule 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 knew 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.

Apparently the Trump campaign was resurrecting the old "Not All Men" argument that used M&M's, 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...

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 anti-semitic material from Nazi Germany. In the heartwarming story Der Giftpilz, 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, Julius Streicher, was executed as a war criminal.

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.

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

Of course it ignores that candy are not individual people with complex, nuanced personalities.

It ignores that a reasonable person has little reason to believe that any candy are poisoned in your average bag of bulk candy.

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.

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.

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.

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. The act of taking a number two can strain your heart and cause a heart attack. 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.

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.

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 recent Angelina Jolie and Brad Pitt divorce filing as something more deserving of headlines than a gossip-column footnote.

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.

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.

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?

Sunday, May 1, 2016

GoRoutines: 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?

Does it die, like pruning a branch off a process tree?

Or does Offspring2 keep running?

I wrote a small test application to find out.

The Test:

 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
package main

import (
 "fmt"
 "time"
)

var chanRunner2 = make(chan string)
var chanRunner1 = make(chan string)
var chanStop1 = make(chan bool)

func main() {

 a := time.NewTimer(5 * time.Second)
 b := time.NewTimer(10 * time.Second)

 go Runner1()

 for {
  select {
  case <-a.C:
   chanStop1 <- true
  case strMessage := <-chanRunner1:
   fmt.Println(strMessage)
  case strMessage := <-chanRunner2:
   fmt.Println(strMessage)
  case <-b.C:
   fmt.Println("DONE!")
   return
  default:
   continue
  }
 }
}

func Runner1() {

 go Runner2()

 c := time.NewTicker(500 * time.Millisecond)

 for {
  <-c.C
  select {
  case <-chanStop1:
   return
  default:
   chanRunner1 <- "Howdy from Runner1!"
  }
 }
}

func Runner2() {

 d := time.NewTicker(500 * time.Millisecond)

 for {
  <-d.C
  chanRunner2 <- "Hello from Runner2!"
 }
}

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.

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.

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.

Expected Output:

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().

Actual Output:

Drumroll, please...

./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!

There it is; Runner2() kept running after Runner1() exited. Something to keep in mind when modeling how your application works!