mirror of
https://github.com/blindlobstar/go-interview-problems
synced 2025-04-23 02:05:14 +00:00
costly connections with unsafe storage problem
This commit is contained in:
parent
ed7f3d2dfe
commit
70173ea8fc
13
05-costly-connections-with-unsafe-storage/README.md
Normal file
13
05-costly-connections-with-unsafe-storage/README.md
Normal file
@ -0,0 +1,13 @@
|
||||
# Costly Connections With Unsafe Storage
|
||||
|
||||
Implement a function `sendAndSave(requests []string, maxConn int)` that:
|
||||
|
||||
* Manages up to `maxConn` concurrent connections.
|
||||
* Ensures connections are properly established before sending requests.
|
||||
* Sends multiple requests using the Send method and saves responses using `UnsafeStorage`.
|
||||
* Prevents corrupt data storage by ensuring safe handling of `UnsafeStorage.Save`.
|
||||
* Properly handles locking and concurrency to avoid race conditions and deadlocks.
|
||||
|
||||
## Tags
|
||||
`Concurrency`
|
||||
|
109
05-costly-connections-with-unsafe-storage/solution/solution.go
Normal file
109
05-costly-connections-with-unsafe-storage/solution/solution.go
Normal file
@ -0,0 +1,109 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
ready bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewConnection() *Connection {
|
||||
return &Connection{}
|
||||
}
|
||||
|
||||
func (c *Connection) Connect() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
c.ready = true
|
||||
}
|
||||
|
||||
func (c *Connection) Disconnect() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
c.ready = false
|
||||
}
|
||||
|
||||
func (c *Connection) Send(req string) (string, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if !c.ready {
|
||||
return "", errors.New("connection is not ready")
|
||||
}
|
||||
|
||||
// Sending request
|
||||
<-time.After(1 * time.Second)
|
||||
return "resp:" + req, nil
|
||||
}
|
||||
|
||||
type UnsafeStorage struct {
|
||||
sem chan struct{}
|
||||
data []string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewUnsafeStorage() *UnsafeStorage {
|
||||
return &UnsafeStorage{sem: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
func (s *UnsafeStorage) Save(data string) {
|
||||
select {
|
||||
case s.sem <- struct{}{}:
|
||||
<-s.sem
|
||||
default:
|
||||
data = "" // corrupt string
|
||||
}
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.data = append(s.data, data)
|
||||
}
|
||||
|
||||
var storage = NewUnsafeStorage()
|
||||
|
||||
// sendAndSave should send all requests concurrently using at most `maxConn` simultaneous connections.
|
||||
// Responses must be saved using UnsafeStorage. Be careful: UnsafeStorage is not safe for concurrent use.
|
||||
func sendAndSave(requests []string, maxConn int) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(len(requests))
|
||||
|
||||
reqCh, respCh := make(chan string, len(requests)), make(chan string, len(requests))
|
||||
for _, req := range requests {
|
||||
reqCh <- req
|
||||
}
|
||||
close(reqCh)
|
||||
|
||||
for range maxConn {
|
||||
go func() {
|
||||
conn := NewConnection()
|
||||
conn.Connect()
|
||||
defer conn.Disconnect()
|
||||
|
||||
for req := range reqCh {
|
||||
resp, err := conn.Send(req)
|
||||
if err == nil {
|
||||
respCh <- resp
|
||||
}
|
||||
wg.Done()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(respCh)
|
||||
}()
|
||||
|
||||
for resp := range respCh {
|
||||
storage.Save(resp)
|
||||
}
|
||||
}
|
76
05-costly-connections-with-unsafe-storage/task.go
Normal file
76
05-costly-connections-with-unsafe-storage/task.go
Normal file
@ -0,0 +1,76 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Connection struct {
|
||||
ready bool
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewConnection() *Connection {
|
||||
return &Connection{}
|
||||
}
|
||||
|
||||
func (c *Connection) Connect() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
c.ready = true
|
||||
}
|
||||
|
||||
func (c *Connection) Disconnect() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
<-time.After(2 * time.Second)
|
||||
c.ready = false
|
||||
}
|
||||
|
||||
func (c *Connection) Send(req string) (string, error) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
if !c.ready {
|
||||
return "", errors.New("connection is not ready")
|
||||
}
|
||||
|
||||
// Sending request
|
||||
<-time.After(1 * time.Second)
|
||||
return "resp:" + req, nil
|
||||
}
|
||||
|
||||
type UnsafeStorage struct {
|
||||
sem chan struct{}
|
||||
data []string
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func NewUnsafeStorage() *UnsafeStorage {
|
||||
return &UnsafeStorage{sem: make(chan struct{}, 1)}
|
||||
}
|
||||
|
||||
func (s *UnsafeStorage) Save(data string) {
|
||||
select {
|
||||
case s.sem <- struct{}{}:
|
||||
<-s.sem
|
||||
default:
|
||||
data = "" // corrupt string
|
||||
}
|
||||
<-time.After(1 * time.Second)
|
||||
|
||||
s.Lock()
|
||||
defer s.Unlock()
|
||||
s.data = append(s.data, data)
|
||||
}
|
||||
|
||||
var storage = NewUnsafeStorage()
|
||||
|
||||
// sendAndSave should send all requests concurrently using at most `maxConn` simultaneous connections.
|
||||
// Responses must be saved using UnsafeStorage. Be careful: UnsafeStorage is not safe for concurrent use.
|
||||
func sendAndSave(requests []string, maxConn int) {
|
||||
}
|
30
05-costly-connections-with-unsafe-storage/task_test.go
Normal file
30
05-costly-connections-with-unsafe-storage/task_test.go
Normal file
@ -0,0 +1,30 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestSendAndSave(t *testing.T) {
|
||||
requests := []string{"req1", "req2", "req3", "req4", "req5"}
|
||||
maxConn := 2
|
||||
expecTime := 8 * time.Second
|
||||
|
||||
start := time.Now()
|
||||
sendAndSave(requests, maxConn)
|
||||
execTime := time.Since(start).Round(time.Second)
|
||||
|
||||
if len(storage.data) != len(requests) {
|
||||
t.Errorf("Expected %d saved items, got %d", len(requests), len(storage.data))
|
||||
}
|
||||
|
||||
for i, data := range storage.data {
|
||||
if data == "" {
|
||||
t.Errorf("data at index %d is corrupted (empty string)", i)
|
||||
}
|
||||
}
|
||||
|
||||
if execTime > expecTime {
|
||||
t.Errorf("func takes too long expected: %d seconds, got %d seconds", expecTime, execTime)
|
||||
}
|
||||
}
|
@ -13,6 +13,7 @@ Here is a list of the problems available in the repository. Problems are organiz
|
||||
* [Equivalent Binary Trees](02-equivalent-binary-trees/)
|
||||
* [Web Crawler](03-web-crawler/)
|
||||
* [Non-Blocking Cache](04-non-blocking-cache/)
|
||||
* [Costly Connection With Unsafe Storage](05-costly-connections-with-unsafe-storage/)
|
||||
|
||||
|
||||
## Contributing
|
||||
|
Loading…
x
Reference in New Issue
Block a user