tag:blogger.com,1999:blog-17763456699946810972024-03-13T21:42:19.825-05:00Stuff I keep Forgetting How to DoJames Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.comBlogger40125tag:blogger.com,1999:blog-1776345669994681097.post-66295481081490214672021-05-20T07:02:00.002-05:002021-05-20T07:02:28.082-05:00Never Start a Goroutine You Can't FinishThe Go programming language has a pair of features that work well together for assembling a sequence of steps into a pipeline: goroutines and channels. In order to use these successfully, the goroutine needs to listen for communication it expects and watch for communication that might happen.
We expect a channel to feed the goroutine items and be closed when no more items are forthcoming. Meanwhile, we need to watch a Context in case the goroutine should exit before the channel is closed. This article will focus on handling some of the edge cases that will keep your goroutines from finishing.
<div class="separator" style="clear: both;"><a href="https://1.bp.blogspot.com/-lTxffyfREoo/YKZNNizgnOI/AAAAAAAAlh0/p4WOtQSqiP0115nCushDVkFNXgP1otoaQCLcBGAsYHQ/s962/goroutines_channels.png" style="display: block; padding: 1em 0px; text-align: center;"><img border="0" data-original-height="234" data-original-width="962" height="98" src="https://1.bp.blogspot.com/-lTxffyfREoo/YKZNNizgnOI/AAAAAAAAlh0/p4WOtQSqiP0115nCushDVkFNXgP1otoaQCLcBGAsYHQ/w400-h98/goroutines_channels.png" title="Goroutines communicating over a channel" width="400" /></a></div>
There are some fundamental things you should understand before using channels and goroutines. Start by completing the section of the <a href="https://tour.golang.org/concurrency/1" target="_blank">tour of concurrency</a>. With an understanding of the fundamentals, we can explore the details of goroutines communicating over a channel.
The goroutine functions will each be responsible for detecting when it's time to finish:
It is important to check in with your context regularly to see if it is "Done."
A closed channel will give a waiting receiver the zero value.
Ranging on channel loops until that channel is closed.
Let's take a close look at two of the more common approaches I've seen. There's a lot to learn by looking at the trade-offs between these two approaches.<div><br /><h3 style="text-align: left;">Infinite for loop</h3><div><div style="text-align: left;"><pre class=" language-plaintext" style="background: rgb(0, 26, 54) !important; box-sizing: inherit; color: #f4f6f9; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; margin-bottom: 0.5em; margin-top: 0.5em; overflow-wrap: break-word; overflow: auto; padding: 1em; tab-size: 4; text-shadow: none; word-break: normal;"><code class=" language-plaintext" style="background-repeat: no-repeat; box-sizing: inherit; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; hyphens: none; line-height: 1.5; position: relative; tab-size: 4; text-shadow: none; word-break: normal; word-spacing: normal; z-index: 1;">func ForInfinity(ctx context.Context, inputChan chan string) func() error {
return func() error {
for {
select {
case input := <-inputChan:
if len(input) == 0 {
return ctx.Err()
}
fmt.Println("logic to handle input ", input)
case <-ctx.Done():
return ctx.Err()
}
}
}
}</code></pre></div></div><div><ul style="text-align: left;"><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">When the <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">inputChan</code> channel is closed, you have to look for the zero value on the channel. The logic of that select case will need to detect the zero value to finish the goroutine – perhaps with a <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">return nil</code> or <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">return ctx.Err()</code>.</li><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">When the context done case is selected, it feels natural to return <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">ctx.Err()</code>. By doing so, the goroutine is reporting the underlying condition that caused it to finish. Depending on the type of context and its state, the <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">ctx.Err()</code> may be nil.</li><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">If more than one select case is ready then one will be <a href="https://tour.golang.org/concurrency/5" target="_blank">chosen at random</a>. Given the undefined nature of having both of these case statements ready, you might consider having the zero-value-detecting logic <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">return ctx.Err().</code> This will ensure your goroutine returns as accurately as possible, even if the channel case was selected.</li></ul><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;">Range on a channel</h3></div><div><pre class=" language-plaintext" style="background: rgb(0, 26, 54) !important; box-sizing: inherit; color: #f4f6f9; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; margin-bottom: 0.5em; margin-top: 0.5em; overflow-wrap: break-word; overflow: auto; padding: 1em; tab-size: 4; text-shadow: none; word-break: normal;"><code class=" language-plaintext" style="background-repeat: no-repeat; box-sizing: inherit; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; hyphens: none; line-height: 1.5; position: relative; tab-size: 4; text-shadow: none; word-break: normal; word-spacing: normal; z-index: 1;">func ForRangeChannel(ctx context.Context, inputChan chan string) func() error {
return func() error {
for input := range inputChan {
select {
case <-ctx.Done():
return ctx.Err()
default:
fmt.Println("logic to handle input ", input)
}
}
return nil
}
}</code></pre></div><div><ul style="text-align: left;"><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">While the goroutine is waiting to receive on <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">inputChan</code>, it will not exit unless the channel is closed. Now our pipeline func is dependent on the channel close. If the Context is “Done,” we won't know it until an item is received from the <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">range inputChan</code>. Upstream pipeline functions should close their stream when finishing.</li><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">Range won't give us the zero-value-infinite-loop, as in the earlier example. The Range will drop out to our final <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">return nil</code> when the channel is closed.</li><li style="background-repeat: no-repeat; box-sizing: inherit; list-style: disc; margin: 0px 0px 0.25rem; padding: 0px;">The context Done case has the same impact here as it did in the earlier example. The difference here is that the <i style="background-repeat: no-repeat; box-sizing: inherit;">Done</i> context will not be discovered until the channel receive occurs — making it even more important that the channels are closed.</li></ul><div><p style="background-color: white; background-repeat: no-repeat; box-sizing: inherit; color: #4c4d4e; font-family: system-ui, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-size: 14.8px; list-style: none; margin: 0px 0px 1rem; padding: 0px;">Be mindful of the flow inside your goroutine to ensure it finishes appropriately. That and lots of tests will ensure your goroutines under normal and exceptional scenarios. Here are a couple of tests to get you started. These are written to exercise the same scenarios for each of the above goroutines.</p></div></div><div><pre class=" language-plaintext" style="background: rgb(0, 26, 54) !important; box-sizing: inherit; color: #f4f6f9; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; margin-bottom: 0.5em; margin-top: 0.5em; overflow-wrap: break-word; overflow: auto; padding: 1em; tab-size: 4; text-shadow: none; word-break: normal;"><code class=" language-plaintext" style="background-repeat: no-repeat; box-sizing: inherit; direction: ltr; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; hyphens: none; line-height: 1.5; position: relative; tab-size: 4; text-shadow: none; word-break: normal; word-spacing: normal; z-index: 1;">func TestForInfinity(t *testing.T) {
t.Run("context is canceled", func(t *testing.T) {
inputChan := make(chan string)
ctx, cancel := context.WithCancel(context.Background())
cancel()
f := ForInfinity(ctx, inputChan)
err := f()
assert.EqualError(t, err, "context canceled")
})
t.Run("closed channel returns without processing", func(t *testing.T) {
inputChan := make(chan string)
close(inputChan)
ctx := context.Background()
f := ForInfinity(ctx, inputChan)
err := f()
assert.NoError(t, err, "closed chanel return nil from ctx.Err()")
})
}
func TestForRangeChannel(t *testing.T) {
t.Run("context is canceled", func(t *testing.T) {
inputChan := make(chan string)
ctx, cancel := context.WithCancel(context.Background())
cancel()
f := ForRangeChannel(ctx, inputChan)
go func() {
//this test will hang without this goroutine
<-time.After(time.Second)
inputChan <- "some value"
}()
err := f()
assert.EqualError(t, err, "context canceled")
})
t.Run("closed channel returns without processing", func(t *testing.T) {
inputChan := make(chan string)
close(inputChan)
f := ForRangeChannel(context.Background(), inputChan)
err := f()
assert.NoError(t, err, "note there is no need to cancel the context, 'range' ends for us")
})
}</code></pre></div><h3 style="text-align: left;"><br /></h3><h3 style="text-align: left;">Summary</h3><div><section class="flex-1" data-v-dacc2eb8="" style="background-color: white; background-repeat: no-repeat; box-sizing: inherit; color: #4c4d4e; flex: 1 1 0%; font-family: system-ui, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-size: 14.8px;"><div block="[object Object]" class="form-field" content="[object Object]" data-v-dacc2eb8="" field="[object Object]" fieldtype="content_tools" page-type="[object Object]" style="background-repeat: no-repeat; box-sizing: inherit; display: flex; flex-direction: column; margin-bottom: 1rem;"><div style="background-repeat: no-repeat; box-sizing: inherit;"><div class="ck-field" style="background-repeat: no-repeat; box-sizing: inherit; user-select: text;"><p style="background-repeat: no-repeat; box-sizing: inherit; list-style: none; margin: 0px 0px 1rem; padding: 0px;">By understanding how channels interact with <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">range</code> and <code style="background: rgb(225, 233, 242) !important; border-radius: 0.1em; box-sizing: inherit; color: #001a36; direction: ltr; display: inline-block; font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; font-size: 13px; hyphens: none; line-height: 1.5; padding: 0.25em 0.4em; tab-size: 4; text-shadow: none; transform: translateY(-0.125em); white-space: pre; word-break: normal; word-spacing: normal;">select</code> you can ensure your goroutine exits when it should. We have used both examples above successfully. Each different design has trade-offs. No matter the logic flow in your goroutines, always ask yourself: “how will this exit?” Then test it.</p><p style="background-repeat: no-repeat; box-sizing: inherit; list-style: none; margin: 0px 0px 1rem; padding: 0px;">I think it's time for another <a href="https://go-proverbs.github.io/" target="_blank">Go Proverb</a>: Never start a goroutine you can't finish!</p><div><br /></div></div></div></div></section><section class="flex-1" data-v-dacc2eb8="" style="background-color: white; background-repeat: no-repeat; box-sizing: inherit; color: #4c4d4e; flex: 1 1 0%; font-family: system-ui, -apple-system, system-ui, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; font-size: 14.8px;"><div class="mb-25" data-v-dacc2eb8="" style="background-repeat: no-repeat; box-sizing: inherit; margin-bottom: 0.25rem;"><div class="inline-block" data-v-dacc2eb8="" style="background-repeat: no-repeat; box-sizing: inherit; display: inline-block;"></div></div></section></div></div>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-83481256294443024312021-01-19T06:57:00.000-06:002021-01-19T06:57:06.659-06:00Golang Method ReceiversThere are different ways for a method to be assigned to a struct. One of the first thing Golang developers learn is that your method can have a value receiver or a pointer receiver.<br />
<br />
Notice below that foo's <i>bar</i> method has a value receiver and accepts zero arguments. Standard stuff. Where it gets interesting is how I call foo.bar in the example main method.
<br />
<pre><source></source>
package main
import (
"fmt"
)
type foo struct {
msg string
}
func (f foo) bar() string {
return f.msg
}
func main() {
receiver := foo{msg: "the value"}
result := foo.bar(receiver)
fmt.Println(result)
}
</pre>
<br />James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-84260737667165876192020-06-05T10:52:00.000-05:002020-06-05T10:52:23.969-05:00Golang Shutdown Flow<br /><br />While doing some enhancements to the Golang microservices at work I came across quite a few calls to logrus.Fatal late in the execution of the service. Some of these particular services are long running processes that consume from Kafka and write to GCP Spanner. The problem with logrus.Fatal when called late in these services lifecycle is that <a href="https://pkg.go.dev/github.com/sirupsen/logrus?tab=doc#Fatal" target="_blank">Fatal internally calls os.Exit(1).</a> So let’s examine why this is bad for the system in which the service is running.<br /><br />On the surface I wonder if a non-recoverable error encountered late in the process is “Fatal”. The interpretation of what “Fatal” means is subjective. At the beginning of the process when reading the configuration file, making external system connections -- this is a Fatal problem because the service can even get started. Some precondition failure -- yeah, that’s Fatal. But if a service has been happily consuming messages and writing transformed data to a database only to encounter a non-recoverable error -- is that “Fatal”? <br /><br />But that’s not what I wanted to show you. What I wanted to show you is why logrus.Fatal(...) is the wrong way to shutdown a service that has encountered a fatal error.<br /><br />First some basics: <a href="https://play.golang.org/p/b8CAlmiVZPH">https://play.golang.org/p/b8CAlmiVZPH</a><div><br /><blockquote class="tr_bq" style="line-height: 1; margin-bottom: 0pt; margin-top: 0pt;">
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;">package main</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;"><br /></span></span>
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;">import (</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>"errors"</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>"fmt"</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;">)</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;"><br /></span></span>
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;">func main() {</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>defer func() {</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>if err := recover(); err != nil {</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>fmt.Println("chance to recover:", err)</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>panic(err)</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>} else {</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>fmt.Println("nothing to recover")</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>}</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>}()</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;"><br /></span></span>
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>fmt.Println("Hello, playground")</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;"><br /></span></span>
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>panic(errors.New("It's a perfect time to panic! -- Woody"))</span></span><br />
<span style="font-family: arial; line-height: 1;"><span style="font-size: 14.6667px; line-height: 1; white-space: pre-wrap;">}</span></span></blockquote>
<br /><br />This is basic Golang: the app panics, the defer will execute, recover() will consume/catch the error, and then we re-panic (just as a naive solution). The panic is reported, and the application returns a non-zero status code.<br /><br />But what about os.Exit(1)? We know that logrus calls os.Exit(int). We need to be aware of the impact upon our defer statements when we use os.Exit(int). As noted in the <a href="https://pkg.go.dev/os?tab=doc#Exit" target="_blank">godoc</a>, os.Exit(int) does <b>not</b> take time to run defer functions. It's just going to shutdown: <a href="https://play.golang.org/p/fB8GnFxEATs">https://play.golang.org/p/fB8GnFxEATs</a><div>
<blockquote class="tr_bq">
package main<br />import (<br /><span style="white-space: pre;"> </span>"fmt"<br /><span style="white-space: pre;"> </span>"os"<br />) </blockquote><blockquote class="tr_bq">func main() {<br /><span style="white-space: pre;"> </span>defer func() {<br /><span style="white-space: pre;"> </span>if err := recover(); err != nil {<br /><span style="white-space: pre;"> </span>fmt.Println("chance to recover:", err)<br /><span style="white-space: pre;"> </span>} else {<br /><span style="white-space: pre;"> </span>fmt.Println("nothing to recover")<br /><span style="white-space: pre;"> </span>}<br /><span style="white-space: pre;"> </span>}()<br /><span style="white-space: pre;"> </span>fmt.Println("Hello, playground")<br /><span style="white-space: pre;"> </span>os.Exit(0)<br />}</blockquote>
</div>
<div>
<br /></div>
<div>
Here the defer does not run. If you were going to gracefully release the connections to Kafka, Spanner, or any other external resource -- that did not happen. For illustration sake I also have this example returning the success zero status code.</div>
<div>
<br /></div>
<div>
There is another way to exit a Golang application. Well, sort of: <a href="https://pkg.go.dev/runtime?tab=doc#Goexit" target="_blank">runtime.Goexit()</a>. As noted in the godoc all registered defer will be executed. However, it only exits from one goroutine. So if this is called in your last goroutine, your service will crash -- in the same fashion as when all goroutines are blocked causing deadlock: <a href="https://play.golang.org/p/z0k56ZMoF8N">https://play.golang.org/p/z0k56ZMoF8N</a></div><div><div></div></div><blockquote><div style="line-height: 1;"><div style="line-height: 1;">package main</div><div style="line-height: 1;"><br /></div><div style="line-height: 1;">import (</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>"log"</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>"runtime"</div><div style="line-height: 1;">)</div><div style="line-height: 1;"><br /></div><div style="line-height: 1;">func main() {</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>defer func() {</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>if err := recover(); err != nil {</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>log.Println("chance to recover:", err)</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>} else {</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>log.Println("nothing to recover")</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>}</div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>}()</div><div style="line-height: 1;"><br /></div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>log.Println("Hello, playground")</div><div style="line-height: 1;"><br /></div><div style="line-height: 1;"><span style="line-height: 1; white-space: pre;"> </span>runtime.Goexit()</div><div style="line-height: 1;">}</div></div><div></div></blockquote><div><br /></div><div>Where does that leave us? Well, it's important to understand the impact of your libraries upon the flow of execution. The logrus.Fatal(...) method is definitely useful. Use it with the full understanding of what it is doing. Use it during service initialization before any defer functions have been registered. Use it when you know you want defer statements to be skipped.</div><div><br /></div>
<h3 style="text-align: left;"><font size="4"><b>
Bonus</b></font>:</h3><div><br /></div>
<div>It is important when fixing these sorts of problems with service shutdown to recognize the significance of your services exit code. Your services exit code is its last communication with the software architecture -- it's dying breath used to wheeze out one little death rattle. Are you running your services in Docker? Kubernetes? </div>
<div>
<br /></div>
<div>
The service exit code is going to communicate to the container if it exited successfully or crashed with an error. In those situations where you were calling logrus.Fatal, you likely do not want to log the error and simply return. That would have your service return zero as exit code communicating success to the software architecture system. Make sure you take into account the pod and the configured <a href="https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/#restart-policy" target="_blank">restartPolicy</a>. If your service is shutting down because of a non-recoverable error you likely want to wheeze out a death rattle of non-zero.</div>
<div>
<br /></div>
</div>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-25104763782368505102017-02-15T10:17:00.003-06:002017-02-16T07:19:55.062-06:00Password Reset Flow With Native Android<h3>
The Problem Scenario</h3>
I want a native Android application that will pull the user back after they have used their mail/SMS client to continue the password reset flow:<br />
<br />
<ol>
<li>using the native app, our user requests password reset via email or SMS</li>
<li>the user presses link in their email or SMS client</li>
<li>the user opens the native app to complete password reset</li>
</ol>
<div>
We write native applications for a richer user experience. For all of the valid security reasons the password reset must be out of phase. By pulling the user back into the native app to complete the password reset flow we can guide them to the native experience that we want to provide. (that we have spent money and time creating)</div>
<br />
<br />
The scenario is defined by the following two gherkin test cases.<br />
<blockquote class="tr_bq">
<b>Given</b> the user has submitted a password reset<br />
<b>And</b> the system has emailed the long url with the website's address<br />
<b>When</b> the user presses on the link in their phone's mail client<br />
<b>Then</b> the native android app is offered to handle the URL<br />
<b>And</b> when the user chooses the native android app they are taken directly to the screen to save a new password</blockquote>
<blockquote class="tr_bq">
<b>Given</b> the user has submitted a password reset<br />
<b>And</b> the system has sent an SMS with a bit.ly shortened URL<br />
<b>When</b> the user presses on the link in their phone's SMS client<br />
<b>Then</b> the native android app should be offered to handle the URL<br />
<b>And</b> when the user chooses the native android app they are taken directly to the screen to save a new password</blockquote>
<h3>
The Android Activity Registration</h3>
In Android we can provide one Activity that handles completing the password reset flow. That Activity needs the appropriate intent filters so the operating system knows it can handle the long and the short URLs:<br />
<br />
<blockquote class="tr_bq">
<activity<br />
android:name=".authentication.CompleteResetPasswordActivity"><br />
<intent-filter><br />
<action android:name="android.intent.action.VIEW"/><br />
<category android:name="android.intent.category.DEFAULT"/><br />
<category android:name="android.intent.category.BROWSABLE"/><br />
<data android:scheme="https" android:host="${host}" android:pathPrefix="/passwordreset"/><br />
</intent-filter><br />
<intent-filter><br />
<action android:name="android.intent.action.VIEW"/><br />
<category android:name="android.intent.category.DEFAULT"/><br />
<category android:name="android.intent.category.BROWSABLE"/><br />
<data android:scheme="https" android:host="m.my.bitly.domain"/><br />
</intent-filter><br />
</activity></blockquote>
<br />
Did you see that "${host}". That is resolved in build.gradle via:<br />
<blockquote class="tr_bq">
....<br />
buildTypes {<br />
debut {<br />
...<br />
manifestPlacehodlers = [host:"qa.mydomain"]<br />
}<br />
release {<br />
...<br />
manifestPlacehodlers = [host:"www.mydomain"]<br />
}<br />
...</blockquote>
<h3>
The Code</h3>
<div>
Android delivers the URL pressed by the user via the getIntent().getData() method. Which is an android.net.Uri instance. Play with it. Massage it. Turn it into whatever you want. For the url shortened Uri you will of course have to resolve that thing into the full URI. Perhaps you will be using either the REST or Android bi.ly API -- https://dev.bitly.com/.</div>
<div>
<br /></div>
<div>
You will notice that your CompletePasswordResetActivity is launched as the root of it's Task that has affinity to the email or messaging app. Tasks are tricky in Android. These aren't things I've had to deal with much in the past. But tasks and task affinity are things you will need to understand if you want to provide this type of user experience. But that's all I'm going to say about that here. Dealing with the task and getting the user back into the "main task" is worthy of it's own post.</div>
<h3>
Quality Assurance</h3>
<div>
There are a plethora of scenarios to test. For the email flow, does the user use gmail, outlook-web, some other web client, some other native mail client? For SMS you have each carrier's custom messaging client as well as Hangouts and other options from Google. I have so far tested with:</div>
<div>
<br /></div>
<div>
<ul>
<li>my Project Fi Nexus 6. I am stuck with Hangouts. It does NOT work. Hangouts launches the link into it's own internal browser.</li>
<li>a Verizon Motorolla Droid. That phone has Messaging, Messaging+ (Verizon's offering), and Hangouts. The above solution works as expected in all three SMS clients. We can open Gmail and press the full URL link as well.</li>
</ul>
</div>
<div>
<br /></div>
<div>
So.... More testing is needed. Samsung has a wide following for sure. So I'll test on a few flavors of those.</div>
<div>
<br /></div>
James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-17316456525889832032016-09-16T19:50:00.001-05:002016-09-17T07:19:44.301-05:00Android Font Settings To Enable Font Variants<div class="tr_bq">
Today I learned that fonts often have settings to enable alternate representations of particular characters. For example Gotham is not a monospaced font. However, if you enabled the "tnum" setting for your Android TextView, then the font will render as monospaced. That is cool!</div>
<br />
It appears Android is supporting a W3 standard with this feature. The documentation has a link that references CSS Fonts. Furthermore, this method was added as part of API 21. So unfortunately your users on older API will not see the awesome column layout you can produce.<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;"><span style="margin-left: auto; margin-right: auto;"><a href="https://developer.android.com/reference/android/widget/TextView.html#setFontFeatureSettings(java.lang.String)" target="_blank"><img border="0" height="72" src="https://3.bp.blogspot.com/-chYiPIHEbOM/V9yCu2EVwnI/AAAAAAAASuw/lzkQZ2Dw7Q4b_MRvlnkKw7-hq-5POfA8gCK4B/s320/setFontFeatureSettings.png" width="320" /></a></span></td></tr>
<tr><td class="tr-caption" style="text-align: center;"><a href="https://developer.android.com/reference/android/widget/TextView.html#setFontFeatureSettings(java.lang.String)" target="_blank">Android TextView Documentation</a></td></tr>
</tbody></table>
<br />
In code this would look something like this:<br />
<blockquote class="tr_bq" style="background-color: white;">
<span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><span style="color: navy; font-weight: bold;">if </span>(Build.VERSION.<span style="color: #660e7a; font-style: italic; font-weight: bold;">SDK_INT </span>>= Build.VERSION_CODES.<span style="color: #660e7a; font-style: italic; font-weight: bold;">LOLLIPOP</span>) {</span><span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;"><span style="color: #660e7a; font-weight: bold;"> grandTotal</span>.setFontFeatureSettings(<span style="color: green; font-weight: bold;">"tnum"</span>);</span><span style="font-family: "courier new" , "courier" , monospace; font-size: x-small;">}</span></blockquote>
or perhaps you are targetting API 21 and can apply the XML setting so that any string put into the field uses the "tnum" or other settings:<br />
<br />
<span style="background-color: white; color: navy; font-family: "menlo"; font-size: 12px; font-weight: bold;"><TextView</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: navy; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 12px; font-weight: bold;"> <br /> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;">:id=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"@+id/grand_total"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 9pt; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 9pt; font-weight: bold;">:layout_width=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;">"wrap_content"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 9pt; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 9pt; font-weight: bold;">:layout_height=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;">"wrap_content"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 9pt; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 9pt; font-weight: bold;">:fontFeatureSettings=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;">"tnum"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 9pt; font-weight: bold;"> tools</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 9pt; font-weight: bold;">:text=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 9pt; font-weight: bold;">"$30.35"</span><span style="background-color: white; font-family: "menlo"; font-size: 9pt;">/</span><span style="background-color: white; font-family: "menlo"; font-size: 9pt;">></span><br />
<span style="background-color: white; font-family: "menlo"; font-size: 9pt;"><br /></span>
Or perhaps you are targeting API 19 and your TextView has a style set that you can override in the values-21 directory:<br />
<span style="background-color: white; font-family: "menlo"; font-size: 12px;"><br /></span>
<span style="background-color: white; font-family: "menlo"; font-size: 12px;"><</span><span style="background-color: white; color: navy; font-family: "menlo"; font-size: 12px; font-weight: bold;">TextView</span><br />
<span style="background-color: white; color: navy; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 12px; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;">:id=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"@+id/three"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;"> style=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"@style/example"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 12px; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;">:layout_width=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"wrap_content"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 12px; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;">:layout_height=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"wrap_content"</span><br />
<span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;"></span><span style="background-color: white; color: #660e7a; font-family: "menlo"; font-size: 12px; font-weight: bold;"> android</span><span style="background-color: white; color: blue; font-family: "menlo"; font-size: 12px; font-weight: bold;">:text=</span><span style="background-color: white; color: green; font-family: "menlo"; font-size: 12px; font-weight: bold;">"$20.13" </span><span style="background-color: white; font-family: "menlo"; font-size: 12px;">/></span><br />
<span style="background-color: white; font-family: menlo; font-size: 9pt;"><br /></span>
<span style="background-color: white; font-family: menlo; font-size: 9pt;">Then you can define a base style configuration in values/styles.xml</span><br />
<pre style="background-color: white; font-family: Menlo; font-size: 9pt;"><blockquote class="tr_bq" style="font-family: Menlo; font-size: 9pt;">
<<span style="color: navy; font-weight: bold;">style </span><span style="color: blue; font-weight: bold;">name=</span><span style="color: green; font-weight: bold;">"example"</span>>
<<span style="color: navy; font-weight: bold;">item </span><span style="color: blue; font-weight: bold;">name=</span><span style="color: green; font-weight: bold;">"android:textSize"</span>>24sp</<span style="color: navy; font-weight: bold;">item</span>>
</<span style="color: navy; font-weight: bold;">style</span>></blockquote>
<pre style="font-family: Menlo; font-size: 9pt;">and then apply the fontFeatureSettings in values-v21/styles.xml</pre>
<blockquote class="tr_bq" style="font-family: Menlo; font-size: 9pt;">
<<span style="color: navy; font-weight: bold;">style </span><span style="color: blue; font-weight: bold;">name=</span><span style="color: green; font-weight: bold;">"example"</span>>
<<span style="color: navy; font-weight: bold;">item </span><span style="color: blue; font-weight: bold;">name=</span><span style="color: green; font-weight: bold;">"android:textSize"</span>>24sp</<span style="color: navy; font-weight: bold;">item</span>>
<<span style="color: navy; font-weight: bold;">item </span><span style="color: blue; font-weight: bold;">name=</span><span style="color: green; font-weight: bold;">"android:fontFeatureSettings"</span>>tnum</<span style="color: navy; font-weight: bold;">item</span>>
</<span style="color: navy; font-weight: bold;">style</span>></blockquote>
</pre>
I think the screenshot below with two emulators illustrates the difference well. You can clearly see that the columns do not line up between the three TextView fields.<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="http://4.bp.blogspot.com/-RqNsgAJBihY/V900cWEvy7I/AAAAAAAASvo/ozd9H3x6cCQllLC_mQdTEWnCBvHqQ06VwCK4B/s1600/Screen%2BShot%2B2016-09-17%2Bat%2B7.17.06%2BAM.png" imageanchor="1" style="margin-left: auto; margin-right: auto;"><img border="0" height="160" src="https://4.bp.blogspot.com/-RqNsgAJBihY/V900cWEvy7I/AAAAAAAASvo/ozd9H3x6cCQllLC_mQdTEWnCBvHqQ06VwCK4B/s320/Screen%2BShot%2B2016-09-17%2Bat%2B7.17.06%2BAM.png" width="320" /></a></td></tr>
<tr><td class="tr-caption" style="text-align: center;">On the right with "tnum"</td></tr>
</tbody></table>
<br />
<br />
<br />James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com1tag:blogger.com,1999:blog-1776345669994681097.post-23343691374128852382016-08-30T07:22:00.001-05:002017-02-16T07:20:53.309-06:00Constant Code Reivew<h4>
Pair programming is constant code review.</h4>
Is there a column on your Kanban board for "Code Review"? Do you pair program? Why would you need that code review column? That column is counter intuitive. It is <b>not </b>an agile software practice. I am not proposing you never code review. Actually the opposite. You are more likely doing constant code review.<br />
<br />
We know that bugs are easier to fix the earlier they are found. So we pair program. Fix the bug as soon as it's typed with an attentive pair. You save time by doing it right the first time. Each time you or your pair inadvertently types a bug is a learning/teaching opportunity. Use these moments to talk and internalize how that bug came through and make a mental note to not let it happen again. When you make the mistake is the best time to learn from the mistake. So make sure your pair is being attentive and reviewing the code you type.<br />
<br />
How big are your stories? Do they take more than one pairing session? Every time you pair-switch the incoming person should be reviewing the code. The review should cover the design patterns in use as well as looking for typical pitfalls where bugs crop up. Sure, there are probably other things that happen during pair switch. Make sure you review the code that came before! Maybe your stories are not bigger than one pairing session. It does not matter. Hold your pair accountable to be an active copilot.<br />
<br />
The Agile Manifesto says "Individuals and interactions over processes and tools". A code review column is putting process over your team members. We already established that you are pair programming. Why would you need to declare code review only happens <i>after</i> the pair team thinks the story is complete. What!? That doesn't make sense. How can the story be finished if it still needs a code review. Invest in your people. Don't let them use a code review column as a safety net that catches problems. The tightrope walker that has no net is much better at their craft than the one using a net. They have to be or it's a really short career (grin). Take away your safety net to get better at YOUR craft.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com2tag:blogger.com,1999:blog-1776345669994681097.post-48192297305606012292014-12-27T07:52:00.001-06:002014-12-27T07:52:21.311-06:00Quick Android RingtoneMy son was making himself at home in his Android phone Christmas present yesterday. He wanted a particular guitar solo as his ringtone. Here's how I put it together. Spoiler alert, it's much easier than this:<br />
<br />
<ol>
<li>Slice out the guitar solo using itunes</li>
<li>Convert the MP3 to ogg</li>
<li>Added the ANDROID_LOOP metadata</li>
<li>Copied the ringtone to the phone</li>
</ol>
<h3>
Slice out the guitar solo using itunes</h3>
<div>
You can configure iTunes to export songs in MP3. You can also tell iTunes to start/stop playing at certain points in a song.</div>
<div>
<ol>
<li>select the song you want to use</li>
<li>CMD-I to open the settings dialog</li>
<li>Go to the Options tag to enter your start/stop time. This will likely take some fiddling to get the slice you want. With these set, the song will only play this section.</li>
<li>Now open the File menu -> Create New Version -> Create MP3 Version</li>
<li>You probably want to go back to the CMD-I properties dialog to clear the start/stop time of this song</li>
</ol>
<div>
</div>
</div>
<br />
<h3>
Convert the MP3 to ogg</h3>
<div>
A drag-n-drop later I had an ogg file out of the mp3 by using <a href="http://www.mediahuman.com/">Media Human</a>. This ogg will work as a ringtone. However there is a long pause before it loops. This is not what we wanted. </div>
<h3>
Added the ANDROID_LOOP metadata</h3>
<div>
I found <a href="http://audacity.sourceforge.net/">Audacity</a> to add the loop metadata key/value pair. Drag-n-drop the ogg file into Audacity. Then File menu -> Export Audio. Choose your destination file location and press the Save button. Now you get a new dialog where you can enter the new metadata key/value pair: ANDROID_LOOP:true.</div>
<div>
<h3>
Copied the ringtone to the phone</h3>
<div>
<a href="https://www.android.com/filetransfer/">Android File Transfer</a> works slick. Drag-n-drop the file from a Finder window into the Ringtones directory of android file transfer. You don't have to disconnect the USB cable, navigate on your phone to Settings->Sounds and pick your ringtone!</div>
</div>
<h3>
Conclusion</h3>
<div>
In the end I could have just used Audacity since my music library is already MP3 format. I did not have to get iTunes to export an AAC into MP3. Audacity will let you select a section of song by clicking and dragging. Then further adjust the start/stop points. Simple go to the same File menu -> Export Selected Audio.</div>
James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com2tag:blogger.com,1999:blog-1776345669994681097.post-65677349568424012862014-03-23T06:48:00.003-05:002014-03-23T06:48:34.372-05:00Scala Play Framework Template ImportsThe template compiler is pretty sweet. Using a template like a function is awesomely simple. If you have a bit of html code that makes up a reused block on more than one page you just factor it out to it's own ___.scala.html. Then reference it with the "magical" @ character.<br />
<br />
However, the error messages that the template engine provides are less-than-detailed. I had created a subdirectory named: app/views/tags. When I tried to use one of the template functions out of that directory I got the error: <span style="background-color: #f5a0a0; color: #730000; font-family: Monaco, 'Lucida Console', monospace; font-size: 14px;">not found: value gallery</span><br />
<br />
So let's take a look at the important parts.<br />
<br />
I have templates:<br />
<br />
<ul>
<li>app/views/main.scala.html</li>
<li>app/views/tags/gallery.scala.html</li>
</ul>
<div>
With this as the important parts of main.scala.html</div>
<br />
<blockquote class="tr_bq">
<span class="s1">@</span>(artist: models.ArtistModel,<br /> tags: List[String])(<span class="s2">implicit</span> artistModel: Option[models.ArtistModel])<span class="s3"> </span><span class="s3">@import tags._</span><html><br /><head>.....</head><br /><body>...<br /><span class="s3">@gallery()</span>....</body></html></blockquote>
<div class="p1">
Hmmmm, "not found: value gallery"? Why can't it find gallery? I imported it. It's formed correctly. Well, this is painfully obvious now, but it took a few minutes for me to reconcile that the "tags: List[String]" is clashing with the "@import tags._" </div>
<br />
<a href="http://www.playframework.com/documentation/2.2.x/ScalaTemplateUseCases">More on the Play Framework Templates</a>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com4tag:blogger.com,1999:blog-1776345669994681097.post-46720324944133377012013-02-23T07:07:00.001-06:002013-02-23T07:07:52.631-06:00Are You Following the Golden Rule?Well, are you? I'm often asking my sons that exact question. The Golden Rule: Treat others the way you want to be treated. Pretty simple. No room for interpretation. I may have heard it growing up. I don't remember. The Golden Rule was core to the culture at (the company formerly known as) A.G. Edwards & Sons. Ben Edwards often referenced it in his monthly news letters as he visited branches. Mr. Edwards would inevitably digress to how the food tasted and how that branch was following the golden rule. But now <i>I</i> digress.<br />
<br />
It occurred to me the other day that The Golden Rule should not just be about how you <b>treat</b> other people. But how you <b>think</b> <b>about</b> other people. To my sons (11 and 8 at this time) this applies to knowing without hesitation that there is no need to say, "Stephen, don't break it!" But as software developers, we should remind ourselves, "This crap code I'm looking at, well, I'm sure the person who wrote it had good intentions and isn't just an idiot." Because remember, the crap code you're looking at just might be your own.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com2tag:blogger.com,1999:blog-1776345669994681097.post-21371481324542705102012-10-02T13:21:00.001-05:002012-10-02T13:21:27.671-05:00Logging Configuration for Testing I realized this morning that putting the name of the test in the logging frameworks (log4j/slf4j) Mapped Diagnostic Context (MDC) gives great context to every logging statement. I'm not sure why I didn't do this before. Well, I've always had a different log4j (and now slf4j) configuration file sitting in src/test/java to change the level of local classes while executing tests. This configuration almost always goes to the console where the main code will log to a file.
<br />
<blockquote>
</blockquote>
This example uses log4j but slf4j has the same MDC class.
<br />
<blockquote>
<pre ><log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
<appender class="org.apache.log4j.ConsoleAppender" name="console">
<param name="Target" value="System.out" />
<layout class="org.apache.log4j.PatternLayout">
<param name="ConversionPattern" value="%d{ISO8601} %X{testMethod} %-5p %c - %m%n" />;
</layout>
</appender>
<root>
<priority value="debug" />
<appender ref="console" />
</root>
</log4j:configuration>
</pre>
</blockquote>
Then, in Java, you'll need a custom TestWatchman that uses the same string 'testmethod' as seen above inside the french braces <code>%X{testMethod}</code>):
<br />
<blockquote>
<pre>/**
* This helpfull little thing works in concert with the log4j configuration.
* There are two steps to it's use:
* <ol>
* <li>declare a Rule in your test: @Rule public TestNameMDC testNameMDC = new TestNameMDC();</li>
* <li>include the %X in the log4j pattern: %X{testMethod}</li>
* </ol>
* Note that the 'testMethod' in french braces above must match the {@link #MDC_KEY} defined here.
*
*/
public class TestNameMDC extends TestWatchman {
private static final String MDC_KEY = "testMethod";
@Override public void starting(FrameworkMethod method) {
MDC.put(MDC_KEY, method.getName());
}
@Override public void finished(FrameworkMethod method) {
MDC.remove(MDC_KEY);
}
public String getMethodName() {
return (String)MDC.get(MDC_KEY);
}
}</code></code></pre>
</blockquote>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-80887674995774848882012-09-25T16:26:00.001-05:002012-09-25T16:26:39.860-05:00Scala on Play 2: List to Json ResponseI've been writing some Scala. My first Scala app as a matter of fact. I looked at lift, I looked at play. I liked play better, so I'm learning Scala within the context of creating an application on the Play 2.0 framework.
<blockquote></blockquote>
So there's this problem. I have a list of objects, created via case class from the DTO layer. I want them returned from the Controller as Json. Oddly, the <a href="https://github.com/playframework/Play20/wiki/Scalajson">documentation on github</a> does not give an example of this use case. I'm sure that if I were more versed in Scala, the solution would be obvious. If I were doing this in java, I would just for-loop over the list of case-class-instances to build a new list of Json. Or, I would annotate the case class (or DTO in java-speak) so that the Json layer would know how to json-ify each instance in the list.
<blockquote></blockquote>
What really surprises me is why doesn't the transformation happen automatically. Just create the Json for me. I don't really care what the names of the fields are in the json. Honestly, it's easier if the json uses the same field names.
<blockquote></blockquote>
But what's the Scala way?
<blockquote></blockquote>
Well, that documentation I liked to indicates that you need to
<blockquote>Json.toJson( Map )</blockquote>
Sure, that makes sense, I want the keys of the map to be the label and the value of the map is the value of the json field.
<blockquote><pre>val jsonObject = Json.toJson(
Map(
"users" -> Seq(
toJson(
Map(
"name" -> toJson("Bob"),
"age" -> toJson(31),
"email" -> toJson("bob@gmail.com")
)
),
toJson(
Map(
"name" -> toJson("Kiki"),
"age" -> toJson(25),
"email" -> JsNull
)
)
)
)
)</pre></blockquote>
Being new to Scala, I have to scream WTF!? Why does the example show making up all the $%#!&@ data! How does that help put it into place? How can I utilize that? Oh, wait, I know. In that controller where I manufacture stupid data! OK, so after calming down, lets go try stackoverflow. I found this:<a href="http://stackoverflow.com/questions/8695335/how-to-render-json-response-in-play-framework-v2-0-latest-build-from-git">http://stackoverflow.com/questions/8695335/how-to-render-json-response-in-play-framework-v2-0-latest-build-from-git</a>
<blockquote></blockquote>
I was really expecting the answer to be a transformation. But no. This stackoverflow answer shows the solution as being an implicit object. Hmmm? I had not heard of those implicit objects.
<blockquote>implicit object BlahFormat extends Format[Blah] {...}</blockquote>
James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com1tag:blogger.com,1999:blog-1776345669994681097.post-39152328090966930932011-10-14T07:14:00.000-05:002011-10-14T07:14:55.421-05:00Import Self Signed CertsDay three of using self signed certs and I was tired of telling chrome to accept it. Under the wrench menu there was an import certificate process. However, for Ubunutu, what it really needed was importing them into <a href="http://code.google.com/p/chromium/wiki/LinuxCertManagement">the OS keystore</a><br />
<br />
First step, install the libnss3-tools:<br />
<blockquote>sudo apt-get install libnss3-tools</blockquote><br />
There is a mention on that page linked above that says the cert needs to be imported as a CA due to a bug. On Natty Ubuntu it was not necessary. I put the following function in my .bashrc<br />
<br />
<blockquote>function trustCert() {<br />
[ $# -ne 2 ] && echo "you need to provide [certificate_nickname] and [certificate_filename]" && return 1<br />
certutil -d sql:$HOME/.pki/nssdb -A -t "P,," -n $1 -i $2<br />
}<br />
</blockquote>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com1tag:blogger.com,1999:blog-1776345669994681097.post-78174163965426673842011-06-10T08:56:00.000-05:002011-06-10T08:56:43.984-05:00Gradle DependenciesI was looking for the command to tell gradle to list the project dependencies (think maven dependency:tree). I came across <a href="http://issues.gradle.org/browse/GRADLE-299">this closed bug</a>. Annoyingly that bug does not say how to actually <b>output</b> the listing.<br />
<br />
Well, it's a command line option (run gradle --help) that was deprecated in favor of:<br />
<blockquote>gradle dependencies</blockquote>James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com7tag:blogger.com,1999:blog-1776345669994681097.post-6449738841470600692011-05-31T11:47:00.000-05:002011-05-31T11:47:46.035-05:00Architecting and Building A startupThis is not my usual flavor of posts in this space. However, it seems like a good place to mention that my users of Book Mobile have been feeling even more abandoned lately as I've left the corporate world to Architect and build a startup. Stealth mode requires that I say nothing about the startup or it's architecture.<br />
<br />
Hopefully those Book Mobile users understand the nature of spare-time-projects and how that spare time ebbs and flows.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com1tag:blogger.com,1999:blog-1776345669994681097.post-45261808470897642442011-01-26T23:35:00.001-06:002011-01-27T20:11:09.004-06:00Ubuntu Maverick Meerkat Android UDEV SettingsThank you <a href="http://esausilva.com/2010/05/13/setting-up-adbusb-drivers-for-android-devices-in-linux-ubuntu/">Esua Silva</a> for referencing the Maverick udev syntax for my 70-android.rules file. I have a nexus one so of course my vendor id is 18D1:<br />
<blockquote>SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0666"</blockquote><br />
The syntax change from Lucid to Maverick should have been easier to find.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-65646063914256176902010-08-29T09:26:00.000-05:002010-08-29T09:26:42.810-05:00Android Book Mobile Version 1.8I've finally released my first cut at Amazon integration. This will hopefully be THE revenue stream. As always Book Mobile is free and ad-free. The search result screen now includes a button to open a web browser with the Amazon page for the current book. As part of this integration I now, for the first time, have some metrics about how much the application is being used. All of this is completely anonymous! The last thing I want is the responsibility keeping personal data safe from attack.<br />
<br />
In the first 24 hours there have been 664 books successfully looked up. The sad part is that only 7 of them where clicked through to Amazon and non of those users bought anything. So far the only revenue is from last week when my wife used a beta version to buy me another Elmore Leonard paperback -- thanks honey!<br />
<br />
This release included a large refactoring of the result screen where most of the error reports are occurring. Please update so I can fix any remaining issues! Better yet, if you are running Froyo you can look for that auto update checkbox on the market application update screen.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com6tag:blogger.com,1999:blog-1776345669994681097.post-60558102475629456322010-07-14T15:01:00.000-05:002010-07-14T15:01:49.901-05:00Microsoft Office Has one Cool FeatureI know that Microsoft Outlook has many pundits. I'm one of them. But that mail client has one thing going for it. When I type "doe snot" when I really meant "does not" -- Outlook would catch the typo and put the "s" where it belonged. Mind you, I don't think this is a redeeming feature.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-74300014057251819782010-04-21T13:33:00.003-05:002010-04-21T13:37:33.710-05:00Eclipse Search in Debug ViewWell, I was debugging Glassfish via a remote connection and needed to find a particular thread to pause. Of course I was looking for the http worker threads. Scanning that long list of threads is just to painful. So I thought, "Can I search this?" I hit F5, and low-n-behold a search window comes up listing the threads. I type "*HttpWorkerThread", select one, and am returned to the Debug view with the same thread selected.<br /><br />Thank you Eclipse. Thank you for just working -- the way software should.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-48581421009939063432010-01-28T10:19:00.003-06:002010-01-28T10:27:48.671-06:00The Downside of Touch Screen DevicesThe downside of touch devices is that my autonomic response is evolving to interact with every gadgets screen. I just tried swiping my finger across the screen of my 5G iPod. <br /><br />I'll be glad when the family budget allows me to upgrade all of the iPods and my wife's phone to a touch screen. Maybe by then <a href="http://www.citizenwatch.com/">Citizen </a>watch will have a touchscreen to replace those buttons on the side of my current model. I'm sure you can think of something that needs a touch screen. It might be interesting to play with one of those new iPads. Every time I've used a touch screen computer in the past I found it lacking the precision I desire. The precision that I'm used to from a mouse.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-82824812045614056682009-10-06T10:06:00.005-05:002009-10-07T07:51:07.134-05:00Continuous IntegrationI've worked in environments with continuous integration for nine years. I introduced the concept at 2 of the 4 companies I worked at over that time. Hopefully everyone developing in an environment supported by continuous integration already takes build pass/fail seriously. If not, I suggest a moment of personal reflection upon your projects continuous integration server. I suggest that everyone might look at the health across multiple projects and multiple builds to assess how well they are doing to keep the projects green. What is your rough estimate of build success on the builds that your commits triggered? In Team City, find the “My Changes” tab at the top. Expand all three (at least I have three) sections. In CruiseControl, hmmm? I'm not sure anymore.<br /><br />I am not trying to incite any sort of competition over who can reach 100% triggered build success. I only suggest that we might think about our personal goals. We might challenge ourselves to never cause a build failure. To aspire to 100% success is wrong. There are going to be a few "oops". There are going to be some environment problems.<br /><br />Project management should always be looking at the percentage of failed builds. Also looking at the volume of commits and the size of commits. Particularly just prior to a release. Continuous integration is a great tool like any other -- the value derived is based on how it is used.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-23276403496882966082009-09-30T22:41:00.002-05:002009-09-30T22:55:11.290-05:00Mixing JSP 2.0 with GWTI've just overcome a pesky litle problem between JSP 2.0 and GWT. I have my JSPs in xml format: <code><jsp:root ...></code>. You are likely aware that this is a bit aggressive about removing whitespace from the resulting html. This includes the transformation of empty body tags into bodyless tags. Ergo this<br /> <code><div id="footer"></div></code><br />becomes this<br /> <code><div id="footer"/></code><br /><br />Now that second one was confusing firefox on my linux workstation. Likely on your platform as well. This is just like the old days when we fought with <code><script src="thefile.js" /></code> tags to make sure they keep their separate closing tag.<br /><br />The solution is just to drop a comment into the previously empty body<br /> <code><div id="footer"><![CDATA[<!-- -->]]></div></code><br /><br /><a href="http://rbtech.blogspot.com/2007/06/gwt-and-jsp-20-oh-pain.html/xml">Thank you Ramon</a>, for pointing me down the path. It's been a long time away from servlet code. It should not have taken me as long as it did to remember this problem.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-74273222438473909112009-09-06T21:58:00.006-05:002010-01-02T12:34:28.330-06:00Android Book Mobile (1 week later)It's been a great first week for ABM on the market. Over 1,400 downloads and still carrying a 4 star rating! Here is a screen shot of the Android Market -- Developer Console. It shows all the applications you have published with their current stats.<br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_LX0ty8ZcDsc/SqR32KgAkxI/AAAAAAAAAQk/TQVrSi13xtI/s1600-h/androidmarket_bookMobile_wk1.png"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 400px; height: 17px;" src="http://3.bp.blogspot.com/_LX0ty8ZcDsc/SqR32KgAkxI/AAAAAAAAAQk/TQVrSi13xtI/s400/androidmarket_bookMobile_wk1.png" alt="" id="BLOGGER_PHOTO_ID_5378555627402466066" border="0" /></a><br />One statistic that would be great if I knew how often people are using it. If I had my own server-side to the application I could gather that statistic myself. But alas, the data being stored at books.google.com, and the reviews coming from goodreads.com, I don't know how much they are using it. I fully expect someone will tell me their phone ran out of memory trying to load their full library.James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com21tag:blogger.com,1999:blog-1776345669994681097.post-20154867695686566162009-08-29T06:07:00.003-05:002009-08-29T06:22:25.907-05:00Android Book MobileI entered my Android application into the <a href="http://http//code.google.com/android/adc/">Android Developer Challenge II</a>. You can read more about what "Book Mobile" does and my thoughts behind the name over at the <a href="http://androidbookmobile.appspot.com/">product website</a>.<br /><ul><li>I've integrated the Google book api into Android.<br /></li><li>I use the <a href="http://http//www.goodreads.com/api">GoodReads</a> api to get reviews for books. </li><li>I integrated with <a href="http://code.google.com/p/zxing/">zxing</a> for barcode scanning to read ISBNs.<br /></li><li>Because of this <a href="http://code.google.com/p/android/issues/detail?id=1073">missing feature in Android</a> I had to draft my own login dialog for users to enter their google account credentials.</li></ul>Now for the frustrating part. How they setup the registration/entry process to the ADC means if I want my application available on the market, I have to repackage. This means changing the AndroidManifest.xml and refactoring all the code to have a new package structure. That doesn't seem so bad, right?James Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0tag:blogger.com,1999:blog-1776345669994681097.post-2741662592130343262009-08-27T09:02:00.003-05:002009-08-27T09:37:07.487-05:00Launch Explorer.exe from CygwinWorking in cygwin, I often need an explorer window to my current directory or a sub-directory there of. I called this script expl.sh.<br /><blockquote>#!/bin/bash<br />export p=`pwd`<br />export pc=`cygpath -w $p/$1`<br />explorer.exe /root,$pc &</blockquote>Once you have this the directory on your path where you keep your scripts: perhaps ~/scripts:<br />expl.sh<br />expl.sh some/sub/directoryJames Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com1tag:blogger.com,1999:blog-1776345669994681097.post-30269343008714674832009-08-24T22:01:00.003-05:002009-08-24T22:07:37.500-05:00Google App Engine and GWTWell, I started poking around with Goole app engine in Eclipse. By default they want to use GWT. fine. Someone once said, "when drinking the tainted cool-aid you might as well eat the cookie too." (if no one said it then I just did)<br /><br />Well, I develop on Ubuntu and immediately ran into an UnsatisfiedLinkError.<br /><blockquote>** Unable to load Mozilla for hosted mode **<br />java.lang.UnsatisfiedLinkError: /home/user/projects/gwt-linux-1.5.3<br />/mozilla-1.7.12/libxpcom.so: libstdc++.so.5:<br /></blockquote>Thankfully, <a href="http://blog.scisurfer.com/2009/07/unable-to-load-mozilla-for-hosted-mode.html">this was the first hit</a> on by search. Thank youJames Wilsonhttp://www.blogger.com/profile/06723635153103504657noreply@blogger.com0