Решение на Concurrent Crawling от Красимир Стойков

Обратно към всички решения

Към профила на Красимир Стойков

Резултати

  • 5 точки от тестове
  • 0 бонус точки
  • 5 точки общо
  • 6 успешни тест(а)
  • 5 неуспешни тест(а)

Код

package main
import (
"errors"
"io/ioutil"
"net/http"
"sync"
"time"
)
func handle(url string, callback func(string) bool) bool {
timeout := time.Duration(3 * time.Second)
client := http.Client{
Timeout: timeout,
}
resp, err := client.Get(url)
if err != nil {
return false
} else {
defer resp.Body.Close()
contents, err := ioutil.ReadAll(resp.Body)
if err != nil {
return false
}
if callback(string(contents)) {
return true
}
return false
}
}
type GoroLock struct {
free bool
sync.RWMutex
}
type UrlLock struct {
buffer []string
position int
count int
sync.RWMutex
}
func SeekAndDestroy(callback func(string) bool, chunkedUrlsToCheck <-chan []string, workersCount int) (string, error) {
if workersCount < 1 {
return "", errors.New("Bad things happen when you are not positive.")
}
if chunkedUrlsToCheck == nil {
return "", errors.New("Bad things happen when you cannot define yourself.")
}
endTime := time.Now().Add(15 * time.Second)
finish := make(chan string)
goroBuffer := []GoroLock{{true, *new(sync.RWMutex)}}
urlLock := UrlLock{make([]string, 100), 0, 0, *new(sync.RWMutex)}
for {
select {
case urls := <-chunkedUrlsToCheck:
for _, url := range urls {
foundFreeGoro := false // check if there are too many running goroutines
for _, goro := range goroBuffer {
goro.Lock()
if goro.free {
goro.free = false
goro.Unlock()
go func(url string) {
if handle(url, callback) {
finish <- url
return
} else {
checkForWaitingUrl: // You may say I'm a psycho, but I'm not the only one.
urlLock.Lock()
if (urlLock.count - urlLock.position) > 0 { // count will only grow up
urlLock.position++ // the position of the next non-handled url into the buffer
urlLock.Unlock()
if handle(urlLock.buffer[urlLock.position], callback) {
finish <- url
return
} else { // the goroutine won't be released until
goto checkForWaitingUrl // there's no left url into the buffer
}
}
urlLock.Unlock()
goro.Lock()
goro.free = true
defer goro.Unlock()
return // goroutine finished it's work, so there is place for one more goro...
}
}(url)
foundFreeGoro = true
break // found free goroutine for this url, no point of more iterations
} else {
goro.Unlock()
}
}
if !foundFreeGoro {
urlLock.Lock()
urlLock.count++
urlLock.buffer = append(urlLock.buffer, url)
urlLock.Unlock()
} else {
foundFreeGoro = false // everything is just fine for now
}
}
case foundUrl := <-finish:
return foundUrl, nil // found url that meets the requirements
default:
if time.Now().After(endTime) {
return "", errors.New("Bad things are(not) happening when nothing is happening.")
}
}
}
}

Лог от изпълнението

[/tmp/go-build092667878/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithNegativeWorkersCount -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	0.005s
[/tmp/go-build266604112/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithZeroWorkersCount -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	0.006s
[/tmp/go-build195058099/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithInvalidCallback -test.timeout=120s]
--- FAIL: TestWithInvalidCallback-2 (1.00 seconds)
	solution_test.go:43: Test exceeded allowed time of 1 seconds: parameter errors should be immediately returned (callback is nil)
FAIL
exit status 1
FAIL	_/tmp/d20150111-16649-tfdtdq	1.005s
[/tmp/go-build829303187/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithNilChannel -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	0.005s
[/tmp/go-build944361765/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithClosedChannelWhenStarting -test.timeout=120s]
--- FAIL: TestWithClosedChannelWhenStarting-2 (1.00 seconds)
	solution_test.go:43: Test exceeded allowed time of 1 seconds: parameter errors should be immediately returned (the urls channel was closed)
FAIL
exit status 1
FAIL	_/tmp/d20150111-16649-tfdtdq	1.005s
[/tmp/go-build915781038/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithClosedChannelMidway -test.timeout=120s]
--- FAIL: TestWithClosedChannelMidway-2 (7.00 seconds)
	solution_test.go:43: Test exceeded allowed time of 7 seconds: the urls channel was closed after 5 seconds
FAIL
exit status 1
FAIL	_/tmp/d20150111-16649-tfdtdq	7.005s
[/tmp/go-build667267617/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWhetherGlobalTimeoutIsHandled -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	15.005s
[/tmp/go-build414943172/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestWithLoremIpsum -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	2.012s
[/tmp/go-build016612825/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestIfTimeoutAndErrorCodesAreHonoured -test.timeout=120s]
--- FAIL: TestIfTimeoutAndErrorCodesAreHonoured-2 (0.01 seconds)
	solution_test.go:267: Function returned 'http://127.0.0.2:56952/page_with_error_code' when it should have returned 'http://127.0.0.2:56952/correct_page'
FAIL
exit status 1
FAIL	_/tmp/d20150111-16649-tfdtdq	0.011s
[/tmp/go-build202053516/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestRaceCondition -test.timeout=120s]
PASS
ok  	_/tmp/d20150111-16649-tfdtdq	1.006s
[/tmp/go-build856888114/_/tmp/d20150111-16649-tfdtdq/_test/d20150111-16649-tfdtdq.test -test.run=TestCloseChannelBeforeFinish -test.timeout=120s]
--- FAIL: TestCloseChannelBeforeFinish-2 (2.00 seconds)
	solution_test.go:316: Function should have returned an error, channel was closed before it could finish
FAIL
exit status 1
FAIL	_/tmp/d20150111-16649-tfdtdq	2.010s

История (1 версия и 0 коментара)

Красимир обнови решението на 11.12.2014 13:32 (преди над 3 години)

+package main
+
+import (
+ "errors"
+ "io/ioutil"
+ "net/http"
+ "sync"
+ "time"
+)
+
+func handle(url string, callback func(string) bool) bool {
+ timeout := time.Duration(3 * time.Second)
+ client := http.Client{
+ Timeout: timeout,
+ }
+ resp, err := client.Get(url)
+ if err != nil {
+ return false
+ } else {
+ defer resp.Body.Close()
+ contents, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return false
+ }
+ if callback(string(contents)) {
+ return true
+ }
+ return false
+ }
+}
+
+type GoroLock struct {
+ free bool
+ sync.RWMutex
+}
+type UrlLock struct {
+ buffer []string
+ position int
+ count int
+ sync.RWMutex
+}
+
+func SeekAndDestroy(callback func(string) bool, chunkedUrlsToCheck <-chan []string, workersCount int) (string, error) {
+ if workersCount < 1 {
+ return "", errors.New("Bad things happen when you are not positive.")
+ }
+ if chunkedUrlsToCheck == nil {
+ return "", errors.New("Bad things happen when you cannot define yourself.")
+ }
+
+ endTime := time.Now().Add(15 * time.Second)
+ finish := make(chan string)
+ goroBuffer := []GoroLock{{true, *new(sync.RWMutex)}}
+ urlLock := UrlLock{make([]string, 100), 0, 0, *new(sync.RWMutex)}
+
+ for {
+ select {
+ case urls := <-chunkedUrlsToCheck:
+ for _, url := range urls {
+ foundFreeGoro := false // check if there are too many running goroutines
+ for _, goro := range goroBuffer {
+ goro.Lock()
+ if goro.free {
+ goro.free = false
+ goro.Unlock()
+ go func(url string) {
+ if handle(url, callback) {
+ finish <- url
+ return
+ } else {
+ checkForWaitingUrl: // You may say I'm a psycho, but I'm not the only one.
+ urlLock.Lock()
+ if (urlLock.count - urlLock.position) > 0 { // count will only grow up
+ urlLock.position++ // the position of the next non-handled url into the buffer
+ urlLock.Unlock()
+ if handle(urlLock.buffer[urlLock.position], callback) {
+ finish <- url
+ return
+ } else { // the goroutine won't be released until
+ goto checkForWaitingUrl // there's no left url into the buffer
+ }
+ }
+ urlLock.Unlock()
+ goro.Lock()
+ goro.free = true
+ defer goro.Unlock()
+ return // goroutine finished it's work, so there is place for one more goro...
+ }
+ }(url)
+ foundFreeGoro = true
+ break // found free goroutine for this url, no point of more iterations
+ } else {
+ goro.Unlock()
+ }
+ }
+ if !foundFreeGoro {
+ urlLock.Lock()
+ urlLock.count++
+ urlLock.buffer = append(urlLock.buffer, url)
+ urlLock.Unlock()
+ } else {
+ foundFreeGoro = false // everything is just fine for now
+ }
+ }
+ case foundUrl := <-finish:
+ return foundUrl, nil // found url that meets the requirements
+ default:
+ if time.Now().After(endTime) {
+ return "", errors.New("Bad things are(not) happening when nothing is happening.")
+ }
+ }
+ }
+}